# 右值引用
左值(赋值操作符“=”的左侧,通常是一个变量)与右值(赋值操作符“=”的右侧,通常是一个常数、表达式、函数调用)之间的差别可以追溯到 Christopher Strachey (C++的祖先语言CPL之父,指称语义学之父)时代。在C++中,左值可被绑定到非const引用,左值或者右值则可被绑定到const引用。但是却没有什么可以绑定到非const的右值(译注:即右值无法被非const的引用绑定),这是为了防止人们修改临时变量的值,这些临时变量在被赋予新的值之前,都会被销毁。例如:
```
void incr(int& a) { ++a; }
int i = 0;
incr(i); // i变为1
//错误:0不是一个左值
incr(0);
//(译注:0不是左值,无法直接绑定到非const引用:int&。
// 假如可行,那么在调用时,将会产生一个值为0的临时变量,
// 用于绑定到int&中,但这个临时变量将在函数返回时被销毁,
// 因而,对于它的任何更改都是没有意义的,
// 所以编译器拒绝将临时变量绑定到非const引用,但对于const的引用,
// 则是可行的)
```
假设incr(0)合法,那么要么产生一个程序员不可见的临时变量并进行无意义的递增操作,要么发生更悲剧的情况:把字面常量“0”的实际值会变成1。尽管后者听起来是天方夜谭,但是对于早期Frotran等这一类把字面常量“0”也存到内存里的某个位置的编译器来说,这真的会变成一个bug。(译注:指的是假如把用于存储字面常量0的内存单元上的值从0修改为1以后,后续所有使用字面常数0的地方实际上都在使用“1”)。
到目前为止,一切都很美好。但考虑如下函数:
```
template<class T> swap(T& a, T& b) // 老式的swap函数
{
T tmp(a);// 现在有两份"a"
a = b; // 现在有两份"b"
b = tmp; // 现在有两份tmp(值同a)
}
```
如果T是一个拷贝代价相当高昂的类型,例如string和vector,那么上述swap()操作也将煞费气力(不过对于标准库来说,我们已经针对string和vector的swap()进行了特化来处理这个问题)。注意这个奇怪的现象,我们的初衷其实并不是为了把这些变量拷来拷去,我是仅仅是想将变量a,b,tmp的值做一个“移动”(译注:即通过tmp来交换a,b的值)。
在C++11中,我们可以定义“移动构造函数(move constructors)”和“移动赋值操作符(move assignments”来“移动”而非复制它们的参数:
```
template<class T> class vector {
// …
vector(const vector&); // 拷贝构造函数
vector(vector&&); // 移动构造函数
vector& operator= (const vector&); // 拷贝赋值函数
vector& operator =(vector&&); // 移动赋值函数
}; //注意:移动构造函数和移动赋值操作符接受
// 非const的右值引用参数,而且通常会对传入的右值引用参数作修改
```
”&&”表示“右值引用”。右值引用可以绑定到右值(但不能绑定到左值):
```
X a;
X f();
X& r1 = a; // 将r1绑定到a(一个左值)
X& r2 = f(); // 错误:f()的返回值是右值,无法绑定
X&& rr1 = f(); // OK:将rr1绑定到临时变量
X&& rr2 = a; // 错误:不能将右值引用rr2绑定到左值a
```
移动赋值操作背后的思想是,“赋值”不一定要通过“拷贝”来做,还可以通过把源对象简单地“偷换”给目标对象来实现。例如对于表达式s1=s2,我们可以不从s2逐字拷贝,而是直接让s1“侵占”s2内部的数据存储(译注:比如char* p),并以某种方式“删除”s1中原有的数据存储(或者干脆把它扔给s2,因为大多情况下s2随后就会被析构)。(译注:仔细体会copy与move的区别。)
我们如何知道源对象能否“移动”呢?我们可以这样告诉编译器:(译注:通过move()操作符)
```
template <class T>
void swap(T& a, T& b) //“完美swap”(大多数情况下)
{
T tmp = move(a); // 变量a现在失效(译注:内部数据被move到tmp中了)
a = move(b); // 变量b现在失效(译注:内部数据被move到a中了,变量a现在“满血复活”了)
b = move(tmp); // 变量tmp现在失效(译注:内部数据被move到b中了,变量b现在“满血复活”了)
}
```
move(x) 意味着“你可以把x当做一个右值”,把move()改名为rval()或许会更好,但是事到如今,move()已经使用很多年了。在C++11中,move()模板函数(参考“brief introduction”),以及右值引用被正式引入。
右值引用同时也可以用作完美转发(perfect forwarding)。(译注:比如某个接口函数什么也不做,只是将工作“委派”给其他工作函数)
在C++11的标准库中,所有的容器都提供了移动构造函数和移动赋值操作符,那些插入新元素的操作,如insert()和push_back(), 也都有了可以接受右值引用的版本。最终的结果是,在没有用户干预的情况下,标准容器和算法的性能都提升了,而这些都应归功于拷贝操作的减少。
参考:
* the C++ draft section ???
* N1385 N1690 N1770 N1855 N1952
* [N2027==06-0097] Howard Hinnant, Bjarne Stroustrup, and Bronek Kozicki:
[A brief introduction to rvalue references](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html)
* [N1377=02-0035] Howard E. Hinnant, Peter Dimov, and Dave Abrahams:
[A Proposal to Add Move Semantics Support to the C++ Language](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm) (original proposal).
* [N2118=06-0188] Howard Hinnant:
[A Proposal to Add an Rvalue Reference to the C++ Language Proposed Wording (Revision 3)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html) (final proposal).
(翻译:dabaitu,感谢:dave)
- C++11 FAQ中文版 - C++11 FAQ
- Stroustrup先生关于中文版的授权许可邮件
- Stroustrup先生关于C++11 FAQ的一些说明
- 关于C++11的一般性的问题
- 您是如何看待C++11的?
- 什么时候C++0x会成为一部正式的标准呢?
- 编译器何时将会实现C++11标准呢?
- 我们何时可以用到新的标准库文件?
- C++0x将提供何种新的语言特性呢?
- C++11会提供哪些新的标准库文件呢?
- C++0x努力要达到的目标有哪些?
- 指导标准委员会的具体设计目标是什么?
- 在哪里可以找到标准委员会的报告?
- 从哪里可以获得有关C++11的学术性和技术性的参考资料?
- 还有哪些地方我可以读到关于 C++0x的资料?
- 有关于C++11的视频吗?
- C++0x难学吗?
- 标准委员会是如何运行的?
- 谁在标准委员会里?
- 实现者应以什么顺序提供C++11特性?
- 将会是C++1x吗?
- 标准中的"concepts"怎么了?
- 有你不喜欢的C++特性吗?
- 关于独立的语言特性的问题
- __cplusplus宏
- alignment(对齐方式)
- 属性(Attributes)
- atomic_operations
- auto – 从初始化中推断数据类型
- C99功能特性
- 枚举类——具有类域和强类型的枚举
- carries_dependency
- 复制和重新抛出异常
- 常量表达式(constexpr)
- decltype – 推断表达式的数据类型
- 控制默认函数——默认或者禁用
- 控制默认函数——移动(move)或者复制(copy)
- 委托构造函数(Delegating constructors)
- 并发性动态初始化和析构
- noexcept – 阻止异常的传播与扩散
- 显式转换操作符
- 扩展整型
- 外部模板声明
- 序列for循环语句
- 返回值类型后置语法
- 类成员的内部初始化
- 继承的构造函数
- 初始化列表
- 内联命名空间
- Lambda表达式
- 用作模板参数的局部类型
- long long(长长整数类型)
- 内存模型
- 预防窄转换
- nullptr——空指针标识
- 对重载(override)的控制: override
- 对重载(override)的控制:final
- POD
- 原生字符串标识
- 右角括号
- 右值引用
- Simple SFINAE rule
- 静态(编译期)断言 — static_assert
- 模板别名(正式的名称为"template typedef")
- 线程本地化存储 (thread_local)
- unicode字符
- 统一初始化的语法和语义
- (广义的)联合体
- 用户定义数据标识(User-defined literals)
- 可变参数模板(Variadic Templates)
- 关于标准库的问题
- abandoning_a_process
- 算法方面的改进
- array
- async()
- atomic_operations
- 条件变量(Condition variables)
- 标准库中容器方面的改进
- std::function 和 std::bind
- std::forward_list
- std::future和std::promise
- 垃圾回收(应用程序二进制接口)
- 无序容器(unordered containers)
- 锁(locks)
- metaprogramming(元编程)and type traits
- 互斥
- 随机数的产生
- 正则表达式(regular expressions)
- 具有作用域的内存分配器
- 共享资源的智能指针——shared_ptr
- smart pointers
- 线程(thread)
- 时间工具程序
- 标准库中的元组(std::tuple)
- unique_ptr
- weak_ptr
- system error