Effective Modern C++(一)

发布时间 2023-06-03 17:31:15作者: icecreamjn

通用引用与右值引用

  我们以T&&的形式声明一个右值引用,但并不是所有形如T&&形式的声明都为右值引用,他还有可能是一个万能引用。

  事实上,“T&&”有两种不同的意思。第一种,当然是右值引用。这种引用表现得正如你所期待的那样:它们只绑定到右值上,并且它们主要的存在原因就是为了识别可以移动操作的对象。

T&&”的另一种意思是,它既可以是右值引用,也可以是左值引用。这种引用在源码里看起来像右值引用(即“T&&”),但是它们可以表现得像是左值引用(即“T&”)。它们的二重性使它们既可以绑定到右值上(就像右值引用),也可以绑定到左值上(就像左值引用)。 形如万能引用的声明需要在对其初始化时才能确定是一个左值引用还是一个右值引用(类似多态?)。

  在两种情况下会出现通用引用。最常见的一种是函数模板形参

template < typename T>
void func(T&& param);

  第二种情况是auto声明符,因为auto的底层机制就是利用模版推导来推导变量类型的

auto&& var2 = var1;                 //var2是一个通用引用

  这两种情况的共同之处就是都存在类型推导type deduction

  对一个通用引用而言,类型推导是必要的,但是它还不够。引用声明的形式必须正确,并且该形式是被限制的。它必须恰好为“T&&

template <typename T>
void f(std::vector<T>&& param);     //param是一个右值引用

  比如以上的param类型声明就不是一个通用引用,因为它的类型是std::vector<T>&& 而不是T&&,所以他是一个右值引用。同样的一个const修饰符也会让它失去成为通用引用的资格。

 

  现在,让我们来总结一下成为通用类型的必要条件:

  1. 变量的类型需要被类型推导,这往往出现在模版内的。
  2. 必须严格遵循形如T&&的格式。

  但值得注意的是,模版内的形如T&&的引用不一定是万能引用,因为他有可能不发生类型推导!(这就是c++,总是存在着各种例外)

  

template<class T, class Allocator = allocator<T>>   //来自C++标准
class vector
{
public:
    void push_back(T&& x);
    …
}

  我们要想判断T&& x是否是一个通用引用,需要先判断它是否需要类型推导,它需要吗?

  让我们想想何时需要调用这个方法,很显然,当我们先有了一个实例,才能调用这个方法。而当我们有了实例,T的类型就已经确定了,这时候再调用push_back就不存在类型推导了。、

  比如当我们将vector声明为std::vector<Widget>时,模版将被实例化为以下代码

class vector<Widget, allocator<Widget>> {
public:
    void push_back(Widget&& x);             //右值引用
    …
};

  但这并不代表所有相似的成员函数都不存在类型推导,比如

template<class T, class Allocator = allocator<T>>   //依旧来自C++标准
class vector {
public:
    template <class... Args>
    void emplace_back(Args&&... args);
    …
};

  这里类型参数(type parameterArgs是独立于vector的类型参数T的,所以Args会在每次emplace_back被调用的时候被推导。

最后

请记住:

  • 如果一个函数模板形参的类型为T&&,并且T需要被推导得知,或者如果一个对象被声明为auto&&,这个形参或者对象就是一个通用引用。
  • 如果类型声明的形式不是标准的type&&,或者如果类型推导没有发生,那么type&&代表一个右值引用。
  • 通用引用,如果它被右值初始化,就会对应地成为右值引用;如果它被左值初始化,就会成为左值引用。