C++ 右值引用与move
右值引用
C++中所有的值都必然属于左值、右值二者之一。左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。
所有的具名变量或者对象都是左值,而右值不具名。很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。左值一般在内存中,右值一般在内存或CPU寄存器中。
左值引用和右值引用的定义:
T & ref = lvalue; T && ref = rvalue;
看个例子:
int main(int argc, char** argv) { // 左值引用 int i = 10; int &l = i; //int &l = 10; // Error cout << l << endl; // 右值引用 int && r = 17; //int && r = i; // Error int *p = &r; cout << *p << endl; return 0; }
可见,右值引用关联到右值时,右值被存储到特定位置,右值引用指向该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置。
既然右值引用可以获取地址,左值引用虽然不能绑定右值,但能绑定右值引用,例如:
int &&r = 10; int &l = r; l = 11; cout << r << endl; // 11
上面的例子中,左值引用只能绑定左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。但是,const左值引用却是个奇葩,它可以算是一个“万能”的引用类型,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。
int main(int argc, char** argv) { // const左值引用 const int &r = 10; //r = 11; // Error const int* p = &r; cout << *p << endl; return 0; }
最后看一个例子:
class A { public: A(int x) :m(x) { cout<< "A(int) called" << endl; } A(const A& other) {m=other.m; cout << "A(const A&)" << endl;} ~A() { cout<< "~A() called" << endl; } int get() {return m;} void set(int x) {m = x;} private: int m; }; A getTemp(int x=10) { return A(x); } void AcceptVal(A a) { } void AcceptRef(const A& a) { } int main(int argc, char** argv) { AcceptVal(getTemp()); // getTemp返回的是右值(临时变量),应该调用两次拷贝构造函数 AcceptRef(getTemp()); // const左值引用绑定右值, 应该只调用一次拷贝构造函数 return 0; }
说明:
getTemp函数返回值会先创建一个临时变量,该临时变量是右值,getTemp返回值拷贝给该变量:
如果将该变量以值传递调用函数AcceptVal(),实参到形参又会发生一次对象拷贝;
如果将该变量以引用传递调用函数AcceptRef(),形参是const左值引用,可以绑定右值(实参),不需要任何拷贝。
PS,以上代码编译需要关闭编译器返回值优化选项,g++ test.cpp -std=c++11 -fno-elide-constructors,否则会发现没有任何拷贝构造函数的调用!
以上,总结一下,其中T
是一个具体类型:
- 左值引用, 使用
T&
, 只能绑定左值; - 右值引用, 使用
T&&
, 只能绑定右值; - 常量左值引用, 使用
const T&
, 既可以绑定左值又可以绑定右值; - 已命名的右值引用,编译器会认为是个左值;
- 编译器有返回值优化,但不要过于依赖;
move操作
move函数在<utility>头文件中。
先看一个例子:
#include <iostream> #include <cstring> #include <vector> using namespace std; class MyString { public: static size_t CCtor; //统计调用拷贝构造函数的次数 // static size_t CCtor; //统计调用拷贝构造函数的次数 public: // 构造函数 MyString(const char* cstr=0){ if (cstr) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); } else { m_data = new char[1]; *m_data = '