右值引用与移动构造
- 这节我们来详细的介绍一下什么是左值引用,什么是右值引用,以及为什么要引入右值引用,还有就是c++11非常重要的特性 -> 移动构造
左值引用和右值引用
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可能可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。左值引用与右值引用一样,一旦引用了一个对象就不能再引用另外一个对象
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名。这里注意,右值出了字面常量以外,都可以理解是一些临时变量 -> 非左值引用返回值之类的
- 注:这里有一个很容易混淆的点,就是右值引用引用的是一个右值,但是这个变量本身是一个左值
int main()
{double x = 1.1, y = 2.2;int&& rr1 = 10;const double&& rr2 = x + y;rr1 = 20;cout << &rr1 << endl;rr2 = 5.5; // 报错return 0;
}
- 这里的rr1是一个左值,它是可以取出地址的,然后rr1=20不是重新绑定一个右值,而是修改其所绑定的右值。
右值引用的使用场景
- 为什么要引入右值引用 -> 为了解决一些左值引用没有完全解决的问题
左值引用解决了传参,和传值返回的问题,但是对于传值返回的问题没有完全解决
->如果返回的是一个局部作用域的对象就不能使用引用返回,但是使用传值返回的话就会进行一份深拷贝,这对于追求极值性能的c++来说不是很可取,所以因右值引用而产生的移动构造就起了作用,它可以很好的解决传值返回深拷贝的问题
- 为什么:因为右值引用往往是临时变量,临时变量一般出了作用域就会进行销毁,那既然你都要销毁了,如果我还要照着你的资源进行一份深拷贝岂不是太浪费了,所以我们可以使用移动构造 -> 其实就是进行资源的转移,将原来要销毁的值转移给要拷贝的值。
对于下面的代码
bit::string to_string(int value){bool flag = true;if (value < 0){flag = false;value = 0 - value;}bit::string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}int main(){bit::string s1;s1 = bit::to_string(1234);return 0;}
如果没有移动构造的话,结果是这样的
candy:~/code/cpp/test/grammar $ ./demo.exe
string(char* str)
string(char* str)
string(const string& s) -- 深拷贝
string& operator=(const string& s) -- 深拷贝
这是在没有编译器优化的情况下,如果有优化会把这次拷贝构造(这是将str拷贝给临时对象用的)去除掉,只剩一次赋值,但还是深拷贝,性能并不高。
移动构造
// 移动构造// 临时创建的对象,不能取地址,用完就要消亡// 深拷贝的类,移动构造才有意义string(string&& s){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}//移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动拷贝" << endl;swap(s);return *this;}
通过对移动构造和赋值的观察我们可以知道,这就是对一个右值(将亡值)进行资源的交换,这样就可以避免深拷贝,从而大大的提高效率
candy:~/code/cpp/test/grammar $ ./demo.exe
string(char* str)
string(char* str)
string(string&& s) -- 移动拷贝
string& operator=(string&& s) -- 移动拷贝
这是有移动构造的结果