目录
一.左值和右值的定义
- 左值在进程的虚拟地址空间中有确定的地址,是存储在内存中的,可以被取地址,
可以出现在赋值符号的左右两边 - 右值可能只是寄存器中的,或者被编译器优化掉,并没有内存地址,所以不能被取地址,只能出现在赋值符号的左边
二. 左值引用和右值引用
Type& r1 = x; Type&& rr1 = y;
- 在语法上,左值引用就用就是给左值取别名,右值引用就用就是给右值取别名
- 左值引用不能引用右值,但是const左值可以引用右值
- 右值不能直接引用左值,但是可以引用move(左值)
- 右值引用表达式的属性是左值,可以被取地址
思考:引用的底层就是指针,如果说右值只是存在寄存器中,那右值引用是如何实现给右值取别名的呢?
绑定到右值引用时,编译器会强制为临时对象分配内存,这样右值引用被取地址指向的地址就是这个被分配的内存
三. 引用延长生命周期
右值引用可用于延长临时对象的生命周期,const左值也可以,但是const的值不能被修改
std::string s1 = "Test";
std::string&& r1 = s1; //错误,右值引用不能绑定左值
const std::string& r2 = s1 + s1;
r2 += "Test"; //错误:const后的值不能被修改
std::string&& r3 = s1 + s1; //右值引⽤延⻓⽣存期
r3 += "Test"; //不加const的右值引用可以被修改
四. 左值和右值的参数匹配
- 形参是左值只能传入左值的实参
- 形参是const左值时,左值右值都能传入
- 形参是右值时,只能传入右值
实参会找出和它最匹配的形参,比如同时有const 左值和右值时,右值的实参就会实例化出右值的那个函数
五. 右值引用的移动语义的使用场景
1.编译器的优化
语法上来说,返回一个对象的返回函数(图一)应该构造生成三个对象,这样·func()
中的ret和copy就浪费了时间和空间
但在编译器的优化下,有时会只构造一个对象
只构造main函数中的str对象,如(图二)


这样来看似乎也能解决了时间空间浪费问题,但有一种情况必须生成临时对象
(如图三),str是个已经存在的对象,无法构造优化,必须对str进行深拷贝
而c++11提供的移动构造和移动赋值就完美解决了这个个问题

2.移动构造和移动赋值
移动构造移动赋值本质上就是掠夺右值对象的资源,上文我们说了,右值对象的生命周期只在当前行,无法被取地址,但右值引用的表达式属性是左值,可以被取地址,对程序来说,出了右值的当前行这个资源就没用了,可以被掠夺,所以在实现移动构造移动赋值时,可以直接交换资源,被赋值或拷贝的对象原来的资源交给右值,随右值的生命周期销毁,被返回的对象资源始终只有一份,这样不管编译器是否优化都不会产生多余的拷贝了
下面是string类的移动赋值和移动构造的简单实现

// 移动赋值
string& operator=(string&& s)
{
std::cout << "string& operator=(string&& s) -- 移动赋值" <<std:: endl;
swap(s);
return *this;
}
// 移动构造
string(string&& s)
{
std::cout << "string(string&& s) -- 移动构造" << std::endl;
swap(s);
}
3.右值移动和移动语义在传参中的提效
- 查看STL文档,C++11后大多容器的insert和push接口都提供了右值版本的接口
- 当实参是⼀个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象
- 当实参是⼀个右值,容器内部则调用移动构造,右值对象的资源到容器空间的对象上
- emplace系列地接口都是右值引用版本的,还涉及到可变参数,同样是C++11的知识,可以看后续讲解


1474

被折叠的 条评论
为什么被折叠?



