C++右值引用和移动语义学习小结
在 C++11 之前,将一个对象移动(move)到另一个对象的通用做法只有 copy constructor 或者 copy assignment ,然后销毁原来的对象。如果这个对象的创建涉及动态内存分配的话,copy constructor 或者 copy assignment 的开销就可能比较大。
从 C++11 开始,C++ 引入了移动语义(move semantics)。由此 C++11 的 class 也多了两个特殊的成员函数 —— move constructor 和 move assignment。
引入移动语义,首先要做的第一件事就是,如何确定该用 move 还是 copy ?
为此 C++11 引入了右值引用这个概念 —— 在 C++ 里所有的右值都可以被移动。
这里又有了另一个问题:什么是右值引用、右值?相对的还有左值引用、左值?
左值与右值这两概念是从 C 语言中传承而来的。在 C 语言中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)。
左值可以取到其内存地址,右值不能。左值与右值的根本区别在于能否获取内存地址。
左值引用和右值引用,其实就是左值的引用和右值的引用。他们俩都是引用,区别在于引用的数据是啥。
注意,左值引用和右值引用都是左值。
下面举几个简单的列子:
int i = 42; // i 是个左值,42 是个右值
int& r = i; // r 是个左值引用
int&& rr = i; // 错误,不能将左值 i 绑定到右值引用 rr 上
int&& rr1 = std::move(i); // std::move 将 i 强制转换成一个右值
int &r2 = i * 42; // 错误: i * 42 是一个右值,不能绑定到一个左值引用
const int &r3 = i * 42; // 可以将一个右值绑定到一个 const 的左值引用
int &&rr2 = i * 42; // 将右值绑定到右值引用
从上面的例子可以看到,有两种引用可以绑定到右值:const 左值引用和右值引用。
当传入的对象是右值且支持 move constructor 或 move assignment 时,C++ 会使用移动语义的函数。如果不支持移动语义的函数,无论传入的对象是右值还是左值,C++ 还是会使用复制语义的函数。
因为左值引用和右值引用其实都是左值, C++11 提供了一个函数 std::move 可以将一个对象强制转换成右值(rvalue)。
移动语义的出现,一方面可以让编译器在某些情况下,使用 move 而不是 copy 来提升程序性能。另一方面,让一些 move-only 的类型在语义上更加明确,如 std::unique_ptr
、std::future
、std::thread
。
更多关于右值引用、移动语义的内容,请参考:
- C++ Primer 5th, 13.6 Moving Objects
- Effective Mordern C++, Chapter 5 Rvalue References, Move Semantics, and Perfect Forwarding
最后,附上之前整理的电子书下载链接:C++ 学习资料整理(电子书)。