右值引用&&
左值与右值
在C++的世界里,值被分类为左值和右值。简单地说,左值是那些在表达式之后仍然存在的对象,如变量。而右值是在表达式结束后不再存在的临时对象,如一些临时产生的值或对象。
我们可以通过一个简单的方法来鉴别它们:**如果你能够取得一个表达式的地址,那么它是左值;否则,它是右值。**例如,具有名字的对象总是左值,而字面量和一些表达式结果是右值。
为了更深入地理解右值,C++11将其进一步细分为纯右值和将亡值。纯右值包括临时变量、某些运算表达式的结果和大部分字面量。将亡值则是与右值引用有关的表达式,它们常常涉及到移动操作。
不过,要记住的是,这两者的细微差别通常不会影响到大部分的应用。在实际编程中,我们经常将它们统一看作右值。
例如下面示例:
class Object {
// some members...
};
Object createObject() {
return Object();
}
int main() {
Object obj; // 'obj' 是一个左值
Object&& tempObj = createObject(); // 'createObject()' 返回一个右值
int a = 42; // 'a' 是一个左值
int&& rvalueRef = a + 5; // 'a + 5' 是一个右值
// 取地址示例
Object* pObj = &obj; // 可以,因为 'obj' 是左值
// Object* pTempObj = &createObject(); // 错误,因为 'createObject()' 是一个右值
return 0;
}
左值引用与右值引用
传统的C++引用(C++98)现在被称为左值引用,它允许我们为变量创建一个别名。而C++11引入了右值引用的概念,使我们能够为右值也创建一个别名。
右值引用的引入极大地丰富了C++的语义,特别是在涉及到移动语义时。通过使用右值引用,我们可以有效地"转移"资源,而不是复制,从而提高效率。
当我们给右值引用命名后,它其实变成了一个左值。
#include <iostream>
#include <utility> // for move
using namespace std;
class Data {
public:
Data() { cout << "Constructor called!" << endl; }
Data(const Data &) { cout << "Copy constructor called!" << endl; }
Data(Data &&) { cout << "Move constructor called!" << endl; }
};
Data createData() {
return Data();
}
int main() {
// 左值引用示例
int x = 10;
int &lvalueRef = x; // 左值引用
lvalueRef += 5;
cout << "x: " << x << endl; // x现在是15
// 右值引用示例
Data &&rvalueRef = createData(); // 调用构造函数但不调用拷贝构造函数
Data dataObj;
Data copiedDataObj = dataObj; // 调用拷贝构造函数
Data movedDataObj = move(dataObj); // 调用移动构造函数
return 0;
}
当一个临时对象(右值)被绑定到一个右值引用时,它的生命周期会延长,与该右值引用的生命周期相同。
通常情况下,临时对象在表达式结束后就被销毁。但是,当它被绑定到右值引用时,这种销毁被延迟,直到该引用的生命周期结束。
引入右值引用的主要目的是实现移动语义。
左值引用只能绑定(关联、指向)左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。
但是,常量左值引用却是个奇葩,它可以算是一个万能的引用类型,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。
int a = 1;
const int& ra = a; // a是非常量左值。
const int b = 1;
const int& rb = b; // b是常量左值。
const int& rc = 1; // 1是右值。
总结一下,其中T是一个具体类型:
- 左值引用, 使用 T&, 只能绑定左值。
- 右值引用, 使用 T&&, 只能绑定右值。
- 已命名的右值引用是左值。
- 常量左值,使用 const T&, 既可以绑定左值又可以绑定右值。