文章首发
【重学C++】04 | 说透C++右值引用、移动语义、完美转发(上)
引言
大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第四讲,在前面《03 | 手撸C++智能指针实战教程》中,我们或多或少接触了右值引用和移动的一些用法。
右值引用是 C++11 标准中一个很重要的特性。第一次接触时,可能会很乱,不清楚它们的目的是什么或者它们解决了什么问题。接下来两节课,我们详细讲讲右值引用及其相关应用。内容很干,注意收藏!
左值 vs 右值
简单来说,左值是指可以使用&符号获取到内存地址的表达式,一般出现在赋值语句的左边,比如变量、数组元素和指针等。- int i = 42;
- i = 43; // ok, i是一个左值
- int* p = &i; // ok, i是一个左值,可以通过&符号获取内存地址
- int& lfoo() { // 返回了一个引用,所以lfoo()返回值是一个左值
- int a = 1;
- return a;
- };
- lfoo() = 42; // ok, lfoo() 是一个左值
- int* p1 = &lfoo(); // ok, lfoo()是一个左值
复制代码 相反,右值是指无法获取到内存地址的表达是,一般出现在赋值语句的右边。常见的有字面值常量、表达式结果、临时对象等。- int rfoo() { // 返回了一个int类型的临时对象,所以rfoo()返回值是一个右值
- return 5;
- };
- int j = 0;
- j = 42; // ok, 42是一个右值
- j = rfoo(); // ok, rfoo()是右值
- int* p2 = &rfoo(); // error, rfoo()是右值,无法获取内存地址
复制代码 左值引用 vs 右值引用
C++中的引用是一种别名,可以通过一个变量名访问另一个变量的值。

上图中,变量a和变量b指向同一块内存地址,也可以说变量a是变量b的别名。
在C++中,引用分为左值引用和右值引用两种类型。左值引用是指对左值进行引用的引用类型,通常使用&符号定义;右值引用是指对右值进行引用的引用类型,通常使用&&符号定义。- class X {...};
- // 接收一个左值引用
- void foo(X& x);
- // 接收一个右值引用
- void foo(X&& x);
- X x;
- foo(x); // 传入参数为左值,调用foo(X&);
- X bar();
- foo(bar()); // 传入参数为右值,调用foo(X&&);
复制代码 所以,通过重载左值引用和右值引用两种函数版本,满足在传入左值和右值时触发不同的函数分支。
值得注意的是,void foo(const X& x);同时接受左值和右值传参。- void foo(const X& x);
- X x;
- foo(x); // ok, foo(const X& x)能够接收左值传参
- X bar();
- foo(bar()); // ok, foo(const X& x)能够接收右值传参
- // 新增右值引用版本
- void foo(X&& x);
- foo(bar()); // ok, 精准匹配调用foo(X&& x)
复制代码 到此,我们先简单对右值和右值引用做个小结:
- 像字面值常量、表达式结果、临时对象等这类无法通过&符号获取变量内存地址的,称为右值。
- 右值引用是一种引用类型,表示对右值进行引用,通常使用&&符号定义。
右值引用主要解决一下两个问题:
这一节我们先详细讲讲右值是如何实现移动效果的,以及相关的注意事项。完美转发篇幅有点多,我们留到下节讲。
复制 vs 移动
假设有一个自定义类X,该类包含一个指针成员变量,该指针指向另一个自定义类对象。假设O占用了很大内存,创建/复制O对象需要较大成本。
[code]class O {public: O() { std::cout |