C++一直号称是比C在类型上更安全的语言,比如C++中可以使用引用(typereference)来在很大范围内代替指针的使用。引用的安全性来自于它的使用受到了编译器的严格控制,必须与一个有址可循的值绑定才可以。所以传统上C++的引用是左值引用(lvalue reference),然而C++11中右值应用(rvalue reference)也被引入成为一个新的语言特性。
让我们先搞清一个概念,什么是左值和右值。请看下面这个表达式:
int a;
a = 1+1;
其中等号左边的a就是左值(lvalue),而等号右边1+1产生了一个为2的右值(rvalue)。两者的区别是在这个表达式执行后,a还可以被访问,而产生了的这个右值2的有效范围却在这个表达式之内,是一个临时的值。
传统上C++的引用是一个左值引用:
int a = 1;
int& lvalueref1 = a;
int& lvalueref2= 2; // 错误,因为2是一个右值
C++11中引入了右值引用:
int a = 1;
int&& rvalueref1 = 2;
int&& ravlueref2 = a; // 错误,右值引用只能绑定到右值
有一点需要注意的就是,不管左值引用还是右值引用,都可以作为一个左值来使用:
int a = 1;
int& b = a;
b = 2; //左值引用作左值
int&& c = 2;
c = 3; // 右值引用作左值
但是问题来了,右值引用到底能干什么呢?要知道创建右值引用是为了和左值引用相区别,所以右值引用最大的用处也在于如此。举个例子:
#include <iostream>
#include <string>
using namespace std;
void printString(string str)
{
cout << str;
}
printString接受所有string类型的值,包括左值和右值。但是每次调用printString时,这个值都会被拷贝一次,效率比较低。通常的改进方式是选择传引用的方式来传值,例如:
void printString(string& str);
但是这种声明无法接受一个右值为参数,例如printString(“ABC”);就被编译器视为不合法。进一步的改进是以const引用的方式传递参数:
void printString(conststring& str);
这样避免了函数调用时的参数拷贝,但是因为是const引用,除非强制转化,在printString函数内部无法修改str的值。引入右值引用之后,就有一个两全其美的办法了,首先重载printString:
void printString(const string& str)
{
cout << str;
}
void printString(string&& str)
{
cout << str;
}
这样,对于入参是右值的时候,例如printString(“ABC”)时,printString(string&& str)的版本会被调用。
更美好的是,我们可以将一个左值伪装成右值,让编译主动选择printString(string&&str)的版本。例如,我们定义了一个string类型的变量hello,明显hello是一个左值,所以printString(hello)肯定调用的是printString(const string& str)。假如我们将hello伪装成一个右值,那么不就可以让编译器选择调用printString(string&& str)了吗!
标准库中提供了一个函数std::move来干这事,std::move将一个左值伪装成一个右值。printString(std::move(hello))时,我们就成功了选择了printString(string&& str)这个版本。
这样做有何意义呢?试想,如果printString不是一个普通函数,而是一个类的构造函数:
class X {
X(const X&);
X(X&&);
};
C++11之前我们只有拷贝构造函数X(constX&),这个函数通过拷贝X对象的一个实例来创建一个新的实例。C++11中,我们有了X(X&&),暂且翻译成传递构造函数(moveconstructor),这个函数不仅可以用一个既有实例创建一个新实例,还可以修改既有实例的内容!现在我们可以创建一种类型,这种类型的实例不可以被拷贝,但是它的内容却可以被传递。例如C++11中的fstream,由于包含文件句柄,不可以拷贝,但现在fstream可以把文件句柄转给另外一个实例了。
参考连接:
· http://www.stroustrup.com/C++11FAQ.html#rval
· http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html