- 左值和右值
- 左值,右值与引用的关系
- 右值的特殊点
- 左值引用的应用场景和短板
- 移动语义
- 关于move
- 完美转发(&& 和 forward)
- 总结
一般没有明确的定义。
左值有以下性质:
- 左值可以取地址
- 左值可以修改(除了const的左值)
- 左值可以放在等号左边,也可以放在等号右边
右值有以下性质:
- 右值不可以取地址
- 右值不可以修改
- 右值只能放在等号右边
最重要的性质是左值可以取地址,右值不可以取地址。
常量,表达式的返回值,非左值的函数返回值是右值
如下:
10 x + y; f(10);
可以参考下图的定义:比较好理解和记忆
C++11对右值进行了严格的区分:
1.C语言中的纯右值,比如:a+b, 100
2.将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。
总结一下, 知道以下几点就足够了
1.右值分为纯右值和将亡值(中间状态)
2.左值可以取地址,右值不可以取地址。
下面代码对4种情况都举了例子:
int main()
{
//左值引用左值
int a;
int& b = a;
//const的左值引用右值
const int& c = 10;
//const的左值引用左值
const int& f = a;
//右值引用右值
//int&& d = a; 右值引用左值是非法的,权限扩大了,右值没有地址
int&& d = 10;
//右值引用move后的左值
int&& e = move(a);//move相当于把左值变成右值了
}
右值的特殊点
右值是不能被修改的,10 = 20这种操作是不允许的。
但是加了右值引用之后,右值可以被修改。并且右值引用是可以取地址的。效果如下:
int&& d = 10; d = 20; cout << &d;
可能会有这种想法,那么加了右值引用的右值不就变得和左值一样了。
答案确实是这样的,后面会讲一个叫完美转换的东西,就和这个性质有关系。
左值引用一般都是用来做函数传参和函数返回值。这样可以减少拷贝次数,提高效率。
但是有一种情况,左值引用没有办法减少拷贝次数。
要返回局部对象的时候,没有办法将函数返回值变成左值引用。 必须拷贝。如果不拷贝,就相当于访问已经析构了的空间了。
右值引用的其中一个应用场景就在这里
ps:并不是说把返回值变成右值引用,右值引用不会做函数返回值!!!
移动语义又分为了移动构造和移动赋值。在C++11里面,6个默认成员函数已经变到8个了,加多了移动构造函数和移动赋值函数。
移动语义需要使用右值引用。
测试代码:如果没有写移动构造的时候,operator+返回的局部对象调用的是深拷贝来返回的。
#includeusing namespace std; class String { public: String(char* str = (char*)"") { if (nullptr == str) str = (char*)""; _str = new char[strlen(str) + 1]; strcpy(_str, str); } String(const String& s) : _str(new char[strlen(s._str) + 1]) { cout << "String(const String& s)" << ' ' << "深拷贝" << endl; strcpy(_str, s._str); } void swap(String& s) { std::swap(_str, s._str); } String& operator=(String&& s) { cout << "String& operator=(String&& s) " << "移动语义" << endl; _str = (char*)""; swap(s); } String& operator=(const String& s) { if (this != &s) { cout << "String& operator=(const String& s) " << "深拷贝" << endl; char* pTemp = new char[strlen(s._str) + 1]; strcpy(pTemp, s._str); delete[] _str; _str = pTemp; } return *this; } String operator+(const String& s) { char* pTemp = new char[strlen(_str) + strlen(s._str) + 1]; strcpy(pTemp, _str); strcpy(pTemp + strlen(_str), s._str); String strRet(pTemp); return strRet; } ~String() { if (_str) delete[] _str; } private: char* _str; }; int main() { String s1((char*)"hello"); String s2((char*)"world"); String s3(s1 + s2); }
原理如下图:
-----------------------------------手动分割线----------------------------------------
上面讲的是没有移动构造函数情况下:现在我们加上移动构造函数。
移动构造函数写法:
1.参数是右值引用
2.交换当前对象和右值引用的所有资源
移动构造和拷贝构造的区别在于:拷贝构造要多开一块空间,移动构造不需要。
移动这个词很形象,就是把资源不断的移动到最终的对象上
String(String&& s)
{
cout << "String(String&& s) " << "移动语义" << endl;
_str = nullptr;
swap(s);
}
再次运行
原理:
总结一下:
move就是把左值变成右值的一个函数。它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
也就是说:只要你不调用移动构造或者移动赋值,左值的值就不会被移动。
一旦发生了移动拷贝,a的字符串就没有了。
完美转发是指一个模板函数,接收到不同的引用参数,可以调用不同的函数去执行。
void f(int&& t)
{
cout << "右值引用" << endl;
}
void f(int& t)
{
cout << "左值引用" << endl;
}
void f(const int& t)
{
cout << "const左值引用" << endl;
}
template
void perfectForward(T&& t)
{
//f(forward(t));
f(t);
}
int main()
{
int&& a = 10;
int& b = a;
const int& c = a;
perfectForward(10);
perfectForward(b);
perfectForward(c);
}
要说两点:
templatevoid perfectForward(T&& t)
上面代码里面的模板T&& t并不是说接收的是右值引用,在模板后面加上**&&代表的是万能引用,既能接收左值引用,也可以接收右值引用。**
必须要加上forward< T >(t)这个函数。我们上面说过,右值在被引用的瞬间就变成左值了,如果不加forward,右值引用在继续传参的时候右值引用的类型就会变成左值引用
因此只要用右值引用传参,就一定要用forward< T >,否则传的就是左值引用。
总结C++11中右值引用主要有以下作用:
- 实现移动语义(移动构造与移动赋值)
- 给中间临时变量取别名:(string&& s = s1 + s2,表达式的返回值也是右值)
- 实现完美转发



