目录
- 什么是左值 / 右值
- 举例
- 左值引用,右值引用
- 常量左值引用
- 移动语义
- 注意事项
什么是左值 / 右值
在C++中,所有的值如果不是左值,就是右值。
左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束后就不再存在的临时对象。
- 有名字的对象都是左值,右值没有名字。
- 还有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。
举例
int ii = 3; //ii是左值,可以取地址,3是右值,无法取地址
int jj = ii+8; //jj是左值,ii+8是右值
T a = getTemp(); //a是左值,getTemp函数的返回值是右值(临时变量)//如果getTemp返回的是引用,则是左值
左值引用,右值引用
C++98中的引l用很常见,就是给变量取个别名,在C++11中,因为增加了右值引l用的概念,所以C++98中的引用都称为了左值引l用。
- 右值引用就是给右值取个名字。
- 语法:
数据类型&& 变量名 = 右值;
- 功能:右值引用就是给右值起名,一旦完成起名,则从临时变量(右值)变成了变量(左值),临时变量(右值)获得新生,也就是拥有了新的生命周期,只要变量名字存在,就一直存活。
#include <iostream>
using namespace std;class AA
{
public:int m_a = 9;
};AA getTemp()
{return AA();
}int main()
{int&& a = 3; //3是右值int b = 8; //b是左值int&& c = b + 5;//b+5是右值AA&& aa = getTemp(); //getTemp()的返回值是右值//右值一旦取了名字,就成了普通变量,也就是成了左值,a是左值,c是左值,aa也是左值cout << a << endl;cout << c << endl;cout << aa.m_a << endl;return 0;
}
常量左值引用
常量左值引用是个奇葩,它可以算是一个万能的引用类型,它可以绑定左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。
语法:const 类型& 变量名 = 左值/右值
在函数传参的时候有时候会用到,挺重要的
移动语义
右值引用的目的就是为了实现移动语义。
引入:
如果一个对象中有堆区资源,需要编写拷贝构造函数和赋值函数,实现深拷贝。
深拷贝把对象中的堆区资源复制了一份,如果源对象(被拷贝的对象)是临时对象,拷贝完就没什么用了,这样会造成没有意义的资源申请和释放操作。
如果能够直接使用源对象拥有的资源,可以节省资源申请和释放的时间。C++11新增加的移动语义就能够做到这一点。
实现移动语义要增加两个函数:移动构造函数和移动赋值函数
移动构造函数的语法:类名(类名&& 源对象){}
移动赋值函数的语法:operator=(类名&& 源对象){}
功能:对右值使用移动语义,效率高很多
#include <iostream>
using namespace std;class AA
{
public:int* m_data = nullptr; //数据成员AA(){}//给数据成员分配内存void alloc(){m_data = new int; //分配内存memset(m_data, 0, sizeof(int)); //初始化}//拷贝AA(const AA& a){cout << "调用了拷贝构造函数\n";if (m_data == nullptr) alloc(); //如果没分配内存,就分配memcpy(m_data, a.m_data, sizeof(int)); //拷贝}//移动构造函数//如果参数是右值会调用这个AA(AA&& a){cout << "调用了移动构造函数\n";if (m_data != nullptr) delete m_data; //如果没分配内存,就分配m_data = a.m_data;a.m_data = nullptr;}//赋值AA& operator=(const AA& a){cout << "调用了赋值函数 \n";if (this == &a) return *this; //避免自我赋值if (m_data == nullptr) alloc();memcpy(m_data, a.m_data, sizeof(int)); //拷贝return *this;}//移动赋值函数//如果参数是右值会调用这个AA& operator=(AA&& a){cout << "调用了移动赋值函数 \n";if (this == &a) return *this; //避免自我赋值if (m_data != nullptr) delete m_data; m_data = a.m_data;a.m_data = nullptr;return *this;}};int main()
{AA a1;a1.alloc();*a1.m_data = 3;cout << *a1.m_data << endl;AA a2 = a1; //调用拷贝构造函数cout << *a2.m_data << endl;AA a3;a3 = a1; //调用赋值函数cout << *a3.m_data << endl;auto f = [] {AA aa; aa.alloc(); *aa.m_data = 8; return aa; };//返回AA类对象的lambda函数AA a4 = f(); //返回的临时变量作为右值,调用移动构造函数cout << *a4.m_data << endl;AA a5;a5 = f(); //调用移动赋值函数cout << *a5.m_data << endl;return 0;
}
注意事项
- 1)对于一个左值,会调用拷贝构造函数,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?
- C++11为了解决这个问题,提供了std::move()方法来将左值转义为右值,从而方便使用移动语义。它其实就是告诉编译器,虽然我是一个左值,但不要对我用拷贝构造函数,用移动构造函数吧。
- 左值对象被转移资源后,不会立刻析构,只有在离开自己的作用域的时候才会析构,如果继续使用左值中的资源,可能会发生意想不到的错误。
AA a1;a1.alloc();*a1.m_data = 3;cout << *a1.m_data << endl;AA a2 = move(a1); //调用移动拷贝构造函数cout << *a2.m_data << endl;
- 2)如果没有提供移动构造/赋值函数,只提供了拷贝构造/赋值函数,编译器找不到移动构造/赋值函数就去寻找拷贝构造/赋值函数。
- 3)C++11中所有容器都实现了移动语义,避免对含有资源的对象发送无谓的拷贝。
- 4)移动语义对于拥有资源(如内存,文件句柄)的对象有效,如果是基本类型,使用移动语义没有意义。