目录
一,引言
在C++11之前没有引出右值的概念,导致一些问题无法得到全面解决,例如如下问题:
cpp
string addStrings(string num1, string num2) {
string str;
int end1 = num1.size() - 1, end2 = num2.size() - 1;
// 进位
int next = 0;
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
str += ('0' + ret);
}
if (next == 1)
str += '1';
reverse(str.begin(), str.end());
return str;
}
如上述问题,返回时不能使用string& ,因为str为一个局部对象。而频繁的拷贝构造造成更多额外的空间消耗。为此引入右值 和移动语义。
二,左值与右值
通常来说能过取地址的为左值,不能取地址的为右值。通常来说右值一般是临时对象,临时变量,常量等等,举一些例子如下:
cpp
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
上述都可以进行取地址,所以被称为左值。
cpp
10;
x + y;
fmin(x, y);
string("11111");
上述都不可以进行取地址,所以被称为右值。第三个函数返回的临时变量,以及最后一个返回的临时对象。
三,左值引用和右值引用
左值引用,在以前的学习中几乎所有的引用都是左值引用。右值引用的格式如下:

在语法设计上:
1,左值引用不能直接引用右值引用,但是const 左值引用可以引用右值引用。如下:

2,右值引用不能直接引用左值,但是右值引用可以引用move(左值)。如下:

3,变量表达式的属性都是左值,也就意味着右值引用变量的属性也是左值。如下:

变量b本身的属性是左值。所有可以被a所引用。
1,生命周期
右值引用可以延长生命周期,上文中讲到右值一般是临时对象等等。因此一般来说右值的生命周期只在本行。但是通过右值引用可以延长右值的生命周期,延长到右值引用变量的生命周期。该变量销毁,该右值的生命周期结束。如下:
cpp
std::string s1 = "Test";
// std::string&& r1 = s1;
const std::string& r2 = s1 + s1;
// r2 += "Test";
std::string&& r3 = s1 + s1;
r3延长了s1+s1结果的生命周期。
2,参数匹配问题
在之前写的函数中,一般参数类型都是const 左值。在传参的过程中,不管是左值还是右值都可以进行接收。那么在C++11新语法下,右值究竟是如何进行匹配的。如下:
cpp
void f(int& x)
{
std::cout << "左值引⽤重载f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << " 到const的左值引⽤重载f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引⽤重载f(" << x << ")\n";
}
cpp
int&& x = 1;f(x); // 调⽤f(int& x)
f(std::move(x)); // 调⽤f(int&& x)
最后两组函数调用更加验证了右值引用表达式本质上是左值。
四,移动构造和移动赋值
在上述引入C++11右值的概念之后就引入了移动构造和移动赋值的概念。如开头的代码,当函数进行如下调用:
cpp
cao::string ret = cao::addStrings("11111", "2222");
cout << ret.c_str() << endl;
在编译器不进行构造优化的情况之下:会出现如图以下情况:

首先"11111"和"2222"会进行构造 出一个临时对象 。这个临时对象 再进行拷贝构造 给addstrings的两个形参。之后是str的构造。str拷贝构造一个临时对象。str析构。临时对象最终拷贝构造给ret。
若类型参数需要很大的空间,则频繁的拷贝构造造成大量空间的消耗。为此引出移动构造。
在编译器不进行构造优化有移动构造的情况如下:

情况和上述一致,之不过将拷贝构造换成了移动构造。而移动构造相当于是一种内存的抢夺,所造成空间消耗很小。为此在这种情况下,移动构造就很有优势。
移动赋值的逻辑和移动构造一致。
五,总结
左值引用和右值引用的目的都是为了减少拷贝,提高效率,其次左值引用还可以修改参数或者返回值。但是在一些情况下并不能返回左值引用。为此想要解决这个问题要么使用输出型参数,要么编译器进行优化,最后就是移动语义的作用。移动语义的作用主要在深拷贝的情况之下。浅拷贝就不需要实现移动语义。