32. 通用引用和右值引用
T&&
有两种不同的含义。一种是右值引用,这种引用的行为就如你想象的那样:它们只能绑定右值,它们主要的存在理由是识别可能被移动的对象。
另一种含义既不是右值引用,也不是左值引用。这种引用在源代码中( T&&
)看起来像右值引用,但是它们可以表现左值引用(即 T&
)的行为。它们的双重性质允许它们绑定右值(就像右值引用那样)和左值(就像左值引用那样)。
而且,它们可以绑定 const 或者非 const 对象,可以绑定 volatile 和非 volatile 对象,还可以绑定 const 和 volatile 同时作用的对象。
它们实际上可以绑定任何东西。这种灵活的引用称为通用引用(Universal Referense)。
出现 &&
的时候,如何判断它是右值引用还是通用引用呢?通用引用有两个条件:
形式必须是精确的
T&&
(const T&&
一定是右值引用)。T
的类型需要推导。
32.1. 通用引用
通用引用在两种语境出现。最常见的就是模板函数参数,第二个语境是 auto 声明。这两种语境有个共同点,就是出现了类型推断。
1int lvalue = 3;
2const auto&& r = lvalue; // 错误:r 是右值引用,不能通过左值初始化
1template<typename T>
2void func(const T&& p);
3
4func(lvalue); // 错误:p 是右值引用,不能通过左值初始化
1template <class T, class Allocator = allocator<T>>
2class vector
3{
4public:
5 void push_back(T&& x); // 右值引用,类模板实例化的时候类型参数已经确定,不存在类型推导。
6};
1template<typename S>
2void func(std::vector<S>&& p); // p 不是通用引用。虽然存在类型推导,但推导的是 std::vector 的类型参数,而不是 T&& 中的T。
1template <class T, class Allocator = allocator<T> >
2class vector
3{
4public:
5 template <class... Args>
6 void emplace_back(Args&&... args); // args 中的每一个参数都是是通用引用
7};
1int i = 10;
2auto&& ai = i; // 通用引用,int&
3
4const int ci = 10;
5auto&& ac = ci; // 通用引用,const int&
32.2. 右值引用变量
Foo&& foo = Foo{10};
一个类型为右值引用的变量,一旦被初始化之后,临时对象的生命将被扩展,会在其被创建的 scope 内始终有效。
因而,看似 foo 被定义的类型为右值引用,但这仅仅约束它的 初始化 :只能从一个右值进行初始化。一旦初始化完成,它就和一个左值引用再也没有任何差别:都是一个已存在对象的 标识 。
1void stupid(Foo&& foo)
2{
3 foo.a += 10; // 在函数体内,foo的性质与一个左值引用毫无差别
4 // blah ...
5}
6
7stupid(Foo{10}); // 在执行函数体之前,进行参数初始化: Foo&& foo = Foo{10}
而临时对象 Foo{10} 的生命周期,会比参数变量 foo 更长。所以将 foo 看作左值引用随意访问,是没有任何风险的。
Note
对于任何类型为右值引用的变量(也包括函数参数),只能由右值来初始化.
一旦初始化完成,右值引用类型的变量,其性质与一个左值引用再也没有任何差别。
类型为右值引用的变量,是一个左值,因而不能赋值给其它类型为右值引用的变量,当然也不能匹配参数类型为右值引用的函数。
32.3. 参考资料
Effective Modern C++ 条款24 区分通用引用和右值引用
C++11中的通用引用
右值引用
C++ 右值引用使用总结