前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++从小白到大牛】C++右值引用与移动语义

【C++从小白到大牛】C++右值引用与移动语义

作者头像
用户11316056
发布2024-10-16 09:30:38
980
发布2024-10-16 09:30:38
举报
文章被收录于专栏:可涵的从小白到大牛的征程

前言:

我们首先汇总一下在C++11中新的变化:

1、新容器 —— unodered_xxx

2、新接口

  • cbegin等,无关痛痒
  • initializer_list系列的构造
  • push_xxx / insert / emplace 等增加右值引用插入版本,意义重大,提高效率
  • 容器新增移动构造和移动赋值,也可以减少拷贝,提高效率

毫无疑问,其中最重要的就是右值引用和移动构造赋值,接下来我们重点讲解有关知识~

一、右值引用

我们首先要清楚跟右值相对的概念,左值和左值引用。

什么是左值,什么是左值引用?

答:

我们平时定义的变量的就是左值。左值是一个表达式,我们可以获取它的地址。一般可以对它进行赋值(加上const变成常量就修改不了)。

左值引用就是给左值取别名。

代码语言:javascript
复制
int& func2()
{
    const int x = 2;
    return x;
}

int main()
{
	// 以下的p、b、c、*p, func2()返回值 都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	
	const int* ptr1 = &c;
	int* ptr2 = &func2();

	printf("%p %p\n", ptr1, ptr2);
    return 0;
}

什么是右值,什么是右值返回?

答:

右值也是表达式,如字面常量、表达式返回值、函数返回值(不能是左值引用返回)。

右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。

右值引用就是对右值的引用,给右值取别名。

代码语言:javascript
复制
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}

注意右值引用是两个&,跟左值引用一个&做区分。

这里的两个函数的返回值哪个是右值,哪个是左值?

func1返回的是右值,因为返回的是x的拷贝,拷贝的临时变量就是右值;而func2返回的是别名,因此就是左值

小总结:

语法上:

能否取地址是区分左值和右值的关键

引用都是别名,不开空间,左值引用是给左值取别名,右值引用是给右值取别名。

底层:(了解)

引用是用指针实现的。左值引用是存当前左值的地址。右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址。

TIP:右值引用与左值引用的交叉

代码语言:javascript
复制
int main()
{
	// 左值引用能否给右值取别名 不能
	// 但是const左值引用可以
	const int& r1 = func1();
	const int& r2 = 10;

	// 右值引用能否给左值取别名 不能
	// 但是右值引用可以给move以后的左值可以
	int x = 0;
	int&& rr1 = move(x);

	return 0;
}

左值引用能否给右值取别名——不能 但是const左值引用可以

右值引用能否给左值取别名——不能 但是右值引用可以给move以后的左值可以

引用的意义:

本质为了减少拷贝!


所以右值引用到底有什么用?(左值引用没有解决所有问题)

我们首先看看左值引用解决了什么问题?

1、传参的拷贝解决了

浅拷贝不用考虑,深拷贝时我们采用引用取别名的方法,将传参不用再额外开辟空间。减少拷贝

2、传返回值的问题解决了一部分

函数调用结束时,返回值仍然存在,不用开辟新空间(引用返回)

但是

局部对象(出了作用域就销毁的对象)返回的拷贝问题,只能传值返回,就存在拷贝,如果有些对象过大!拷贝会消耗巨大的问题没有解决!

注意深拷贝是一种极度的资源浪费,因为深拷贝过后,临时创建的对象马上又销毁。

C++11之前,编译器已经做了不小的努力去减少拷贝。但是并没有从本质上解决问题。

为了真正解决问题就需要我们的右值引用!

C++11对右值概念的解释,细分便于理解

1、纯右值(内置类型的右值)如:10 / a + b

2、将亡值(自定义类型的右值)如:匿名对象、传值返回函数

C++提供右值引用,本质是为了参数匹配时,区分左值和右值,看到底是调用移动构造还是普通深拷贝

什么是移动构造呢?

假如有string s2(tmp),系统识别到了这个tmp是右值将亡值,就会调用移动拷贝,直接将tmp的资源和s2进行交换! 认为如果是将亡值进行深拷贝就是极度的浪费!因为tmp马上又销毁

原码:

代码语言:javascript
复制
// 移动构造
string(string&& s)
 :_str(nullptr)
 ,_size(0)
 ,_capacity(0)
{
 cout << "string(string&& s) -- 移动语义" << endl;
 swap(s);
}
int main()
{
 bit::string ret2 = bit::to_string(-1234);
 return 0;
}

但是如果tmp是左值,就会老老实实进行深拷贝

优化之前:

解释:

原本的str是左值,但是会有拷贝构造产生的临时值,也就是右值(将亡值),这里利用将亡值的特性使用移动构造,因此是1次拷贝,1次移动。

优化之后:

直接隐式将原本的左值str move()转换变成右值,这样就可以不用再创建临时对象进行拷贝构造,直接一次移动构造就可以完成!!!

C++11后的优化点:

1、将一次拷贝、一次移动合二为一,省去中间的临时对象

2、隐式的强行对move(str)识别为右值

总结:

浅拷贝的类不需要移动构造

深拷贝的类才需要移动构造

深拷贝对象传值返回只需要移动资源,代价很低。

左值引用没有解决的问题,右值引用解决了。深拷贝对象传值返回只需要移动资源,代价很低。C++11后,所有容器都增加了移动构造和移动赋值

问题:右值不能改变,那怎么转移你的资源呢?

答:

右值被右值引用后,右值引用的属性是左值,可以被改变,这样资源才能被转移!

注意正是因为右值引用的属性还是左值,所以我们在传参的时候还是会调用左值引用,因此在传参的地方都需要move()一下,保证右值调用的是右值引用。

右值引用延长了资源的生命周期!!!并不是延长对象的生命周期。

总结:

右值引用并不是直接起作用的,将返回值move后进行右值返回,是不行的,并没有解决临时变量返回值生命周期的问题,因此右值引用并不是直接起作用的,是间接起作用。我们需要重新书写一个移动构造,在返回值为临时变量时,会将这个临时变量隐式转换为右值(move一下),这样就调用我们的移动构造!就构成了我们的移动语义!

move和forward的区别:

move:将左值属性变成右值属性

forward:保持属性:

  • 本身是左值,就不变
  • 本身是右值,右值引用后,属性是左值,转成右值,相当于move一下
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 一、右值引用
    • 什么是左值,什么是左值引用?
      • 什么是右值,什么是右值返回?
        • 小总结:
          • 语法上:
          • 底层:(了解)
        • TIP:右值引用与左值引用的交叉
          • 引用的意义:
          • 所以右值引用到底有什么用?(左值引用没有解决所有问题)
            • 我们首先看看左值引用解决了什么问题?
              • 什么是移动构造呢?
                • 优化之前:
                  • 优化之后:
                    • C++11后的优化点:
                      • 总结:
                        • 问题:右值不能改变,那怎么转移你的资源呢?
                        • 总结:
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档