C++11引入了许多新特性,其中右值引用(Rvalue References)是一个非常重要的概念,它极大地增强了C++处理临时对象和移动语义的能力。右值引用通过类型后加&&来标识,它允许我们区分对象的值类别(左值或右值),进而可以编写更灵活、效率更高的代码。
1. 左值与右值
在深入右值引用之前,了解左值(Lvalue)和右值(Rvalue)的概念是很重要的。
2. 左值与右值的区分
如果可以对表达式使用&运算符并得到一个有效的地址,那么该表达式是一个左值。
如果对表达式使用&运算符导致编译错误,那么该表达式是一个右值。
通过decltype可以获取表达式的类型,并使用std::is_lvalue_reference和std::is_rvalue_reference来检查这个类型是否是左值引用或右值引用。
注意:这种方法实际上是在检测表达式是否可以绑定到左值引用或右值引用,而不是直接判断左值或右值。
这些函数模板通常用于显式地将左值转换为右值引用,但它们本身并不直接用于区分左值和右值。不过,它们的使用场景暗示了表达式的预期角色(作为右值传递)。
现代编译器和集成开发环境(IDE)通常会提供关于表达式类型的反馈,这可以帮助你理解一个表达式是左值还是右值。
理解代码的上下文和编程习惯也是区分左值和右值的重要方法。例如,理解函数返回值的类型(是值、指针、引用还是右值引用)以及这些值如何被使用。
3.1 完美转发(Perfect Forwarding)
通过使用std::forward和模板,右值引用允许函数将其参数转发给另一个函数,而保持参数的左值性或右值性不变。这在编写通用库时非常有用,如标准库中的std::make_unique和std::make_shared。
3.2 移动语义(Move Semantics)
移动语义允许资源(如动态分配的内存、文件句柄等)从一个对象“移动”到另一个对象,而不是被复制。这通常通过定义一个移动构造函数和一个移动赋值操作符来实现,它们接受右值引用作为参数。使用移动语义可以显著提高涉及大量数据转移的代码的性能。
假设我们有一个简单的字符串类,它使用动态分配的内存来存储字符串内容。
#include
#include
#include // for std::move
class String {
public:
char* data;
// 构造函数
String(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 移动构造函数
String(String&& other) noexcept
: data(other.data) {
other.data = nullptr;
}
// 析构函数
~String() {
delete[] data;
}
// 移动赋值操作符
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
return *this;
}
// 其他成员函数...
};
// 使用示例
int main() {
String s1("Hello");
String s2 = std::move(s1); // 使用移动构造函数
// 此时s1的data被置为nullptr,s2接管了原本s1的资源
}
附:c++11新增的其他性