右值引用的目的主要是为了是减少内存拷贝,优化性能。
左值右值赋值操作符“=”的左侧,通常是一个变量赋值操作符“=”的右侧,通常是一个常数、表达式、函数调用表达式结束后依然存在的持久化对象表达式结束时就不再存在的临时对象左值可以运用&操作符取得地址右值(临时对象)无法取得地址左值一般有名字右值一般没有名字C++11以前,就有左值引用,简称引用,左值引用就像是给变量起了别名,操作这个别名就跟操作原变量名一样。
int x = 20; int& rx = x; // 定义左值引用时必须初始化C++11以前,右值被认为是无用的资源,而C++11引入右值引用,就是为了重用右值。定义右值引用需要使用&&:
int&& rrx = 200; //rrx本身是一个左值,但是里面存放的是200这个右值的地址,只要rrx仍存在,200这个右值就不会被销毁。 int& lx = rrx; //lx等于10,对lx的改变同样会作用到rrx右值引用只可以引用右值:
X a; X f(); X& r1 = a; // 将r1绑定到a(一个左值) X& r2 = f(); // 错误:f()的返回值是右值,无法绑定 X&& rr1 = f(); // OK:将rr1绑定到临时变量 X&& rr2 = a; // 错误:不能将右值引用rr2绑定到左值a移动语义:
C++11引入右值引用的意义就是为了实现移动语义。比如,类的右值是一个临时对象,如果没有被绑定到右值引用上,这个临时对象在表达式结束时就会被废弃。所以我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。其中以同类型的右值构造对象时,需要以右值引用形式传入参数,然后移走这个临时对象的资源(以前是复制临时对象的资源)。
比如用c++实现一个字符串类MyString,MyString内部开辟和管理一个C语言的char *数组,这个时候一般都需要自己实现拷贝构造函数和拷贝赋值函数(因为C++默认的拷贝是浅拷贝,只会直接复制指针,但指针这种资源不能共享,不然一个析构了,另一个也就完蛋了)
int main() { vector<MyString> vecStr; vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000 for(int i=0;i<1000;i++){ vecStr.push_back(MyString("hello")); } cout << MyString::CCtor << endl; }push_back函数会对传递进来的参数进行一次拷贝(调用拷贝构造函数),并将其添加到vector中。因此上述代码会执行1000次拷贝构造函数。在每次迭代中执行的操作如下:
调用MyString("hello")构造出临时对象,这个临时对象是一个右值。把临时对象复制一个(调用MyString的拷贝构造函数),放到VecStr中。MyString("hello")只是临时对象,拷贝完就没用了,进行析构释放。下一轮又重新构造临时对象以及拷贝。最理想的情况下,临时对象构造出来后,应该直接交给VecStr,而不是把内容复制一遍后析构掉,造成没有意义的资源申请和释放操作。而C++11新增加的移动语义就能够做到这一点。
要实现移动语义就必须增加两个函数:移动构造函数和移动赋值构造函数。以移动构造函数为例,对比普通的拷贝构造函数和移动构造函数:
// 普通的拷贝构造函数 MyString(const MyString& str) { m_data = new char[ strlen(str.m_data) + 1 ]; strcpy(m_data, str.m_data); } // 移动构造函数 MyString(MyString&& str) noexcept:m_data(str.m_data) { str.m_data = nullptr; //不再指向之前的资源了 }MyString("hello")是个临时对象,是个右值,优先进入移动构造函数而不是拷贝构造函数(移动构造函数参数使用右值引用),它并不是重新分配一块新的空间、将要拷贝的对象复制过来,而是"偷"了过来,即复制临时对象的指针,使得自己的指针指向临时对象的资源,然后将临时对象的指针修改为nullptr,这样临时析构的时候就无法释放其资源(否则它把资源释放了,那偷来的资源就没有了)。
复制与移动的区别如下,可以看到move构造是直接抢了临时对象的资源:
对于一个左值,肯定是调用拷贝构造函数了,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。我觉得它其实就是告诉编译器,虽然我是一个左值,但是不要对我用拷贝构造函数,而是用移动构造函数吧。
更具体的讲解和例子看[c++11]我理解的右值引用、移动语义和完美转发,这博客讲的很好。