1. 左值和右值
一些场景下,把表达式等号左边的称为左值,右边的称为右值。但并不绝对:
int a = 1;
int b = a;
第一行,a是左值,而1是右值;第二行,b是左值,a也是左值
在C++中左值一般指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。
右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的。
基于这一特征可以用取地址符&来判断左值和右值,能取到内存地址的值都是左值,否则为右值。
注意: x++是右值,因为要先生成一个x值的临时变量,在对这个临时变量递增,返回这个临时变量;
++x是左值,是直接对x递增,返回x本身
通常,字面量都是一个右值,除了字符串字面量外
"hello world"; //这是一个右值
编译器会将字符串字面量存储到程序的数据段中,程序加载的时候也会为其开辟内存空间,所以也可以用取地址符&来获取字符串字面量的内存地址。
2. 左值引用
非常量左值的引用必须是一个左值,而常量左值引用还可以引用右值
int &x = 1; // 编译错误
const int &x = 11; // 编译成功
第二行的11的在语句结束后生命周期被延长。
常量左值引用 引用右值的典型例子在复制构造函数和重载赋值运算符函数中有着巨大作用。
例如:
class X{
public:
X() {}
X(const X&) {} // &在X后面,是因为X是类,相当于是一个类型
X& operator = (const X&) {return *this;}
};
X make_x() {
return X();
}
int main() {
X x1;
X x2(x1);
X x3(make_x());
x3 = make_x();
}
注意,函数make_x()的返回值是一个临时对象,是一个右值,所以若复制构造函数和重载赋值运算符函数的参数不是常量左值引用,则会编译报错
形参里面都是左值,和实惨无关
一旦使用了常量左值引用,就表示我们无法在函数内修改该对象的内容,除了强制类型转换。
所以需要右值引用来帮助我们完成这项工作。
3. 右值引用
左值引用是在类型后添加&, 右值引用则是在类型后加&&
不能用右值引用去引用一个左值,会引起编译错误
右值引用的特点之一是可以延长右值的生命周期,但这并不是最终目标,其真实目标是减少对象复制,提升程序性能。
4. 右值的性能优化空间
5. 移动语义
移动构造函数, 其参数是一个右值引用,是浅拷贝
而复制构造函数的参数是一个左值引用,是深拷贝
使用移动构造函数性能会得到提升,没有进行内存复制,而是简单的指针替换操作
除此之外,移动赋值运算符函数也能完成移动操作。
6.值类别
值类别是表达式的一种属性
C++11根据该属性将表达式分为三个类别:左值,纯右值,将亡值
左值和纯右值和C++98中的概念相同
将亡值的两种途径:
第一种是使用类型转换将泛左值转换为该类型的右值引用,比如:
static_cast<A&&>(myA)
std::move将左值转换成右值,该函数的内部也是用static_cast做类型转换
第二种在C++17中引入,称为临时量实质化,指的是纯右值转换到临时对象的过程。
7. 将左值转化为右值
static_cast将左值转换成将亡值
int i = 0;
int &&k = static_cast<int&&>(i); // 编译成功
由于转换的并不是右值,因此它依然有着和转换之前相同的生命周期和内存地址。
最大的作用是让左值使用移动语义
更推荐使用函数模板 std::move, 其内部使用的也是static_cast
8. 万能引用和引用折叠
所谓的万能引用是发生了类型推导
比如模板中,或者auto
万能引用的形式必须是T&&或者auto&&,也就是说 它们必须在初始化的时候被直接推导出来,如果在推导中出现中间过 程,则不是一个万能引用
万能引用能够灵活地引用对象,是因为——引用折叠
引用折叠的规则:
只要有左值引用参与进来,最后推导的结果就是一个左值引用。只有实际类型是一个非引用类型或者右值引用类型时,最后推导出来的才 是一个右值引用。
9. 完美转发
右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值,并不是他原来的类型。(所有形参都是左值)
需要一种方法能够按照参数原来的类型转发到另一个函数,这种转发类型称为完美转发。
std::forward 这个函数模板,这个模板参数是万能引用,在函数内部也是使用static_cast,
作用是在函数模板中,完全依照模板的参数类型(即保持参数的左值,右值特征),将参数传递给函数模板中调用的另外一个函数。
std::move一定会将实参转换为一个右值引用,并且使用std::move不需要指定模板实参,模板实参是由函数调用推导出来的。而std::forward会根据左值和右值的实际情况进行转发,在使用的时候需要指定模板实参。
- std::move无条件转换到右值。就其本身而言,它没有move任何东西。
- std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
- std::move和std::forward在运行期都没有做任何事情。
参考资料:
现代C++语言核心特性解析
标签:std,函数,右值,左值,引用,模板 From: https://www.cnblogs.com/Yuqi0/p/17198704.html