Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

03 May 2014

C++1x的右值引用

原文发表在这里

C++一直号称是比C在类型上更安全的语言,比如C++中可以使用引用(type reference)来在很大范围内代替指针的使用。引用的安全性来自于它的使用受到了编译器的严格控制,必须与一个有址可循的值绑定才可以。所以传统上C++的引用是左值引用(lvalue reference),然而C++11中右值应用(rvalue reference)也被引入成为一个新的语言特性。

让我们先搞清一个概念,什么是左值和右值。请看下面这个表达式:

其中等号左边的a就是左值(lvalue),而等号右边1+1产生了一个为2的右值(rvalue)。两者的区别是在这个表达式执行后,a还可以被访问,而产生了的这个右值2的有效范围却在这个表达式之内,是一个临时的值。

传统上C++的引用是一个左值引用:

C++11中引入了右值引用:

有一点需要注意的就是,不管左值引用还是右值引用,都可以作为一个左值来使用:

但是问题来了,右值引用到底能干什么呢?要知道创建右值引用是为了和左值引用相区别,所以右值引用最大的用处也在于如此。举个例子:

printString接受所有string类型的值,包括左值和右值。但是每次调用printString时,这个值都会被拷贝一次,效率比较低。通常的改进方式是选择传引用的方式来传值,例如:

但是这种声明无法接受一个右值为参数,例如printString(“ABC”);就被编译器视为不合法。进一步的改进是以const引用的方式传递参数:

这样避免了函数调用时的参数拷贝,但是因为是const引用,除非强制转化,在printString函数内部无法修改str的值。引入右值引用之后,就有一个两全其美的办法了,首先重载printString:

这样,对于入参是右值的时候,例如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不是一个普通函数,而是一个类的构造函数:

C++11之前我们只有拷贝构造函数X(const X&),这个函数通过拷贝X对象的一个实例来创建一个新的实例。C++11中,我们有了X(X&&),暂且翻译成传递构造函数(move constructor),这个函数不仅可以用一个既有实例创建一个新实例,还可以修改既有实例的内容!现在我们可以创建一种类型,这种类型的实例不可以被拷贝,但是它的内容却可以被传递。例如C++11中的fstream,由于包含文件句柄,不可以拷贝,但现在fstream可以把文件句柄转给另外一个实例了。

参考连接:

Categories

Tags