Item 28: Understand reference collapsing.

Effective Modern C++ Item 28 的学习和解读。

引用折叠规则

所谓引用折叠(reference collapsing)就是引用指向引用(reference to reference)会折叠(或者坍塌)成一种引用。我们知道,引用分为左值引用和右值引用,因此,引用指向引用就存在 4 种情况:

  1. 左值引用指向左值引用,记为 A& &
  2. 左值引用指向右值引用,记为 A& &&
  3. 右值引用指向左值引用,记为 A&& &
  4. 右值引用指向右值引用,记为 A&& &&

引用折叠的规则为:

只要两个引用中的一个为左值引用的话,则折叠为左值引用,否则为右值引用。

应用上述引用折叠规则,引用指向引用的 4 种情况的结果为:

  1. A& & —> A&
  2. A& && ----> A&
  3. A&& & ----> A&
  4. A&& && ----> A&&

根据引用折叠规则,我们看一下引用折叠应用的几种应用场景。

万能引用的实例化

对于万能引用:

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

我们在 Item 24 中介绍过,万能引用的参数(param)的初始化决定了它代表一个右值还是一个左值。如果初始化为一个右值,万能引用对应右值引用。如果初始化为一个左值,万能引用对应一个左值引用。

这里其实是应用了引用折叠规则。首先类型 T 根据传递给 param 的参数是左值还是右值进行推导,推导的机制为:

如果一个左值传递给 param,T 被推导为一个左值引用;如果一个右值传递给 param,T 被推导成一个非引用类型。

我们先看传递一个左值的情况:

Widget w;
func(w);

首先,T 被推导成一个左值引用,这里为 Widget&,我们用它实例化模板,得到:

void func(Widget& && param);

然后应用引用折叠规则,得到:

void func(Widget& param);

因此,初始化为一个左值,万能引用对应一个左值引用。

再看传递一个右值的情况:

Widget widgetFactory();
func(widgetFactory());

首先,T 被推导成一个非引用类型,这里为 Widget,我们用它实例化模板,得到:

void func(Widget&& param);

这里没有引用指向引用的情况,因此,如果初始化为一个右值,万能引用对应右值引用。

以上就解释了万能引用如何根据初始化参数推导模板参数类型的。

std::forward 机制

引用折叠也是 std::forward 机制的关键部分。看一个 std::forward 应用于万能引用参数的例子:

template<typename T>
void f(T&& fParam)
{// do some work
  someFunc(std::forward<T>(fParam)); // forward fParam to someFunc
} 

根据上面的介绍,我们知道, T 的推导类型取决于 fParam 被初始参数为左值还是右值:如果一个左值传递给 param,T 被推导为一个左值引用;如果一个右值传递给 param,T 被推导成一个非引用类型。

template<typename T> 
T&& forward(typename remove_reference<T>::type& param)
{
  return static_cast<T&&>(param);
}

如果 fParam 被初始化为一个左值类型 Widget,则 T 被推导为 Widget&,则 std::forward 被实例化成 std::forward<Widget&>:

Widget& && forward(typename remove_reference<Widget&>::type& param)
{
  return static_cast<Widget& &&>(param);
}

std::remove_reference<Widget&>::type 产生 Widget,则 std::forward 变为:

Widget& && forward(Widget& param)
{ return static_cast<Widget& &&>(param); }

再应用引用折叠规则,则 std::forward 变为:

Widget& forward(Widget& param)
{ return static_cast<Widget&>(param); }

正如你所见,当一个左值传递给模板函数 f,std::forward 的入参和返回值类型都是一个左值引用,由于 param 的类型已经是一个 Widget&,因此 std::forward 内部的 cast 啥也没干。这完全符合对 std::forward 的预期,传入左值 std::forward 则返回左值(左值引用本质上就是左值),实际上并没有做任何类型 cast。

再来看 fParam 被初始化为一个右值类型 Widget,则 T 被推导为 Widget,则 std::forward 被实例化成 std::forward<Widget>:

Widget&& forward(typename remove_reference<Widget>::type& param)
{ return static_cast<Widget&&>(param); }

std::remove_reference 产生 Widget,则 std::forward 变为:

Widget&& forward(Widget& param)
{ return static_cast<Widget&&>(param); }

这里没有引用折叠的事情,结果也是符合我们对 std::forward 的预期:传入右值 std::forward 则返回右值(把左值参数 param 转化为右值)。

生成 auto 变量

再看 auto 变量的情况,也是类似模板类型的:

auto&& w1 = w;

这其实也是一个万能引用(见 Item 24)。如果用一个左值初始化 w1,auto 类型被推导成 Widget&,则上面代码则变成一个引用指向引用的表达式:

Widget& && w1 = w;

应用引用折叠,则变为:

Widget& w1 = w;

w1 的结果为一个左值引用。

另一方面,用一个右值初始化 w2:

auto&& w2 = widgetFactory();

auto 被推导成非引用类型 Widget,则上述代码变为:

Widget&& w2 = widgetFactory();

w2 的结果为一个右值引用。

typedef 类型别名

若使用 typedef 时候发生引用指向引用的情况,则同样应用引用折叠规则:

template<typename T>
class Widget {
public:
  typedef T&& RvalueRefToT;};

假设使用左值引用实例化:

Widget<int&> w;

这里将产生引用指向引用的表达式:

typedef int& && RvalueRefToT;

应用引用折叠规则,表达式变为:

typedef int& RvalueRefToT;

decltype 类型推导

当分析 decltype 产生的类型时,存在引用指向引用的表达式,也是引用折叠的应用场景。可以参阅 Item 3

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值