文章目录
- 1. 左值和右值
- 1.1 什么是左值
- 1.2 什么是右值
- 2. 左值引用和右值引用
- 2.1 左值引用的使用场景
- 2.2 右值引用的使用场景
- 3.移动语义
- 4. 完美转发
1. 左值和右值
1.1 什么是左值
左值,不能根据名字来判断,即左边的就是左值,这个是错误的。
int a = 10; //a是左值 10是右值
int b = a; //b是左值 a也是左值
左值是一个表示数据的表达式,可以对其取地址,一般可以对它进行赋值。左值既可以出现在赋值符号的左边,又可以在赋值符号的右边
对于
const
修饰的左值,不能给它赋值,但是可以取地址
1.2 什么是右值
右值也是一个数据的表达式,如:字面常量、表达式返回值等。右值不能出现在赋值符号的左边,且右值不能进行取地址
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int a = 10;
int b = a;
//右值
10;
a + b;
Sub(a, b);
return 0;
}
2. 左值引用和右值引用
在语法上来说,引用就是取别名。那么左值引用就是给左值取别名;右值引用就是给右值取别名
void t1()
{
int a = 1;
//左值引用
int& r1 = a; //给左值取别名
const int& r2 = 10; //加上const修饰,给右值取别名
//右值引用
int&& r3 = 10; //给右值取别名
int&& r4 = std::move(a); //加上move给左值取别名
}
2.1 左值引用的使用场景
左值引用可以做参数、做返回值,这样可以减少拷贝。
但是对于局部对象的返回,左值引用无法处理。
string& func() { string str("hello"); return str; //函数结束str销毁了 } int main() { string s = func(); cout << s << endl; return 0; }
那这样就只能传值返回,如果对象比较大,那么这样的代价就比较大
2.2 右值引用的使用场景
对于内置类型的右值,我们通常称为纯右值;对于自定义类型的右值,我们称为将亡值。
而上面的func()
就是一个将亡值,那么我们就可以采用移动拷贝,直接拿到这个将亡值的资源
mystring::string func()
{
mystring::string str("hello");
return str; //函数退出时,str销毁
}
int main()
{
mystring::string s1 = func();
cout << endl;
mystring::string s2;
s2 = func();
return 0;
}
这里的
func()
中的返回值str
被编译器(C++每个编译器都这样)识别成了右值,如果不这样,那么就得加上move(str)
,才能匹配重载的右值引用版本,这样就得修改大部分的代码,C++11的性能就难以直接提升;所以直接识别成右值,就能直接匹配上。例如下面这段代码
vector<vector<int>> func() { vector<vector<int>> vv; //... // //... return vv; }
编译器直接将
vv
识别成了右值,就极大的减少了拷贝,提升了性能。
3.移动语义
当一个右值引用一个左值时,可以通过move
函数将左值转换成右值
这里
s1
被move
处理之后,被识别成了右值,然后会调用移动构造,将资源转移给了s3
4. 完美转发
模板中的&&
叫做万能引用。
如果实参是左值,那么就是左值引用(引用折叠)
如果实参是右值,那么就是右值引用
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
这里运行发现,竟然全部都是左值引用
右值,是不能修改的;而右值引用,是可以修改的,而右值引用的本质又是左值
void t2()
{
//10是右值
int&& ra = 10;
//ra是右值引用,本质上是开了一块空间将10的值存起来
//10++; //error
//cout << &10 << endl; //error
ra++;
cout << &ra << endl;
}
所以上面代码的t
右值引用变量的属性识别成了左值,从而导致全部调用的左值引用函数
如果不识别出左值属性,那么在移动构造的场景下,就不能修改,无法进行资源转移
想要其保持原有的属性,即左值引用保持左值属性,右值引用保持右值属性。我们可以使用forward
函数让其保持原有属性
的t
右值引用变量的属性识别成了左值,从而导致全部调用的左值引用函数
如果不识别出左值属性,那么在移动构造的场景下,就不能修改,无法进行资源转移
想要其保持原有的属性,即左值引用保持左值属性,右值引用保持右值属性。我们可以使用forward
函数让其保持原有属性。
那么本次的分享就到这里,我们下期再见,如果还有下期的话。