1.介绍
右值引用是C++11引入的一种重要特性,它主要用于实现移动语义和完美转发,从而提高程序的性能与效率。
2.左值与右值
在C++中,表达式可以分为左值与右值。左值通常是可以取地址的表达式,例如变量名。右值则是不能取地址的临时对象、字面量等。
判断一个表达式是左值还是右值常用的方法有两种。
(1)位于赋值号(=)左侧的表达式是左值,反之,只能位于右侧的表达式就是右值。
例如:
int a = 1;
1 = a;//错误,1不能为左值
(2)有名称的、可以获取存储地址的表达式即为左值,反之则为右值。
例如,上边的a可以通过&a获取地址,所以其为左值;反之字面量1既没有名称、也无法获取地址,因此为右值。
注意,上边两种方法只适用于大部分场景。本节主要介绍右值引用,这里不在赘述,感兴趣的读者可自行了解。
3.右值引用的用法
之前的C++标准中有引用,但只能操作左值,无法对右值添加引用。例如:
int num = 10;
int &b = num; //正确
int &c = 10; //错误
如上所示,编译器允许我们对num左值建立引用,但不能对10这个右值建立引用。因此,这种引用又称为左值引用。
注意,虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。
因此,C++11引入了另一种引用方式,称为右值引用,用“&&”表示。
右值引用的声明语法如下:
类型 && 引用名 = 右值表达式;
例如:
int num = 10;
//int && a = num; //右值引用不能初始化为左值
int && a = 10;
4.右值引用的用途
(1)移动语义。
它允许我们将一个对象的资源从一个对象转移到另一个对象,而不是进行深拷贝。通过移动语义,可以避免不必要的内存分配与数据赋值,从而提高程序的性能。
通过移动构造函数实现,举个例子:
class demo{
public:
//普通构造函数
//移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
private:
int *num;
};
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。
除此之外,C++还提供了一个move()函数,将左值强制转换为右值。实现左值初始化同类对象也通过移动构造函数实现。
(2) 完美转发
完美转发是指函数模版可以将自己的参数“完美”地转发给内部调用的其他函数。“完美”指步进能准确地转发参数值,还能保证被转发参数的左。右值属性不变。例如:
//实现完美转发的函数模板
template <typename T>
void function(T&& t) {
otherdef(forward<T>(t));
}
上边即为实现完美转发的函数模版,其中forward()为C++11标准的开发者引入的一个模版函数。(其详细实现过程比较复杂,所以直接上结论,感兴趣的可以自行研究)。
5.总结
右值引用的核心目的就是为了实现移动语义(解决深拷贝带来的性能问题)与完美转发(参数传递过程中保持其原始左值或右值属性)。
注意事项:
(1)生命周期:右值引用绑定的临时对象生命周期会被延长,知到右值引用的生命周期结束。但需要注意的是,一旦临时对象的资源被移动,就不能使用该临时对象。
(2)noexcept 声明:移动构造函数通常应该声明为 noexcept,这样可以让标准库容器在进行元素移动时更加高效。