右值引用和通用引用
C++ 一到店,所有人都便都看着他笑……「对呀对呀!……引用有四种写法,你知道么?」
没有引用
编程初学者写一个交换函数,很可能是这样的
1 | void swap(int x, int y) |
显然这样是不起作用的。有些人会从汇编的角度分析,有些人会从调用方式的角度分析。
最后修正后的 C 语言版本使用了指针。(使用宏的都拖出去续了)
1 | void swap(int* x, int* y) |
因为不恰当的使用指针很容易造成空指针的问题。所以 C++ 引入了引用。
左值引用
C++ 使用引用版本的交换函数
1 | void swap(int& x, int& y) |
引用本质上是自动解引用的指针。所以这段代码跟指针的版本原理差不多。
这时候的 C++ 还是守序善良。短短几年过去却变成了混乱邪恶的 C++ 11 。
右值引用
C++ 11 引入了右值引用,用 &&
表示,对应的之前的引用称为左值引用,用 &
表示。使用右值引用的交换函数版本
1 | void swap(int&& x, int&& y) |
然而内置类型并没有移动构造函数,所以移动退化为复制。C++ 11 之前都是拷贝交换,C++ 11 引入右值引用后才实现了真正上的移动交换。如果使用自定义类区别就会明显一些。
通用引用
通用的移动交换函数应该是这样的
1 | template<typename T> |
T
可以是 int
、int &
和 int &&
等等,代入后可能特化为以下版本
1 | void swap(int & x, int & y); |
这样就出现了两个问题。
本来只有左值引用的时候模板函数只需要一两个特化版本。现在引入右值引用后还要写一个版本。
第二个问题是 int&& &
是什么类型?
为了解决第一个问题,C++ 11 引入了通用引用,用 &&
表示。使用通用引用的版本
1 | template<typename T> |
那么问题又来了。通用引用(&&
)和右值引用(&&
)是一样的吗?
答案是不一样的。那要怎么区分呢?
第二个问题的回答刚好解决了这个新问题。
实际上 C++ 是不允许创建引用的引用的。所以右值引用才可以用 && 表示,不然到底是左值引用的左值引用还是单纯的右值引用?
但在 C++ 中还是有办法绕过限制,比如上面使用模板的方法来实现 int& &
的效果。
创建引用的引用是没有意义的,直接使用引用即可。所以有以下规约
1 | & & -> & |
其中 & && -> &
和 && && -> &&
都不会改变原有引用的左右性,所以参数声明为 &&
就表示,如果是左值引用,那么传进来还是左值引用。如果是右值引用,那么传进来还是右值引用。
因此 Scott Meyers 把 &&
命名为通用引用。
相应地模板只需要写一个 T&&
的特化版就可以了,这样的特性被称为完璧转发。
从规约上看,具体的类型如 int&&
就是右值引用,通用的类型 T&&
则是通用引用,左右性需要看传入参数的左右性。