• 本节中,我们来讨论“万能引用”这种抽象的本质,以及 std::forward 的实现机理。

  • Item 23和24中说明了当使用万能引用参数 T&& 时,入参的类型信息会被记录在 T 中。规则实际上很简单:当入参为左值时,T(注意,不是 T&&)被推导为左值引用;当入参为右值时,T 被推导为非引用(注意这里的不对称性)。以一个例子来说明:

template<typename T>
void func(T&& param);Widget widgetFactory(); // function returning rvalue
Widget w;               // a variable (an lvalue)func(w);               // call func with lvalue; T deduced to be Widget&
func(widgetFactory());  // call func with rvalue; T deduced to be Widget
  • 我们需要了解的另一个规则是,C++中不允许出现对引用的引用。但是由以上规则,如果万能引用接收的入参是左值,那么经实例化的函数 func 的签名似乎应该为:
void func(Widget& && param);

明显违反了这条规则。这就涉及了本节的主题引用折叠(reference collapsing):尽管你不能声明对引用的引用,但编译器在一些情景中(模板实例化就是一种)可以产生它们。我们知道,C++中有左值和右值引用,于是可能出现四种对引用的引用的组合:左值-左值,左值-右值,右值-左值,右值-右值。接下来,编译器会将这些引用根据以下规则“塌缩”为一层引用:只要两层引用中有任意一个是左值引用,那么结果就是左值引用,否则结果是右值引用。也就是前三种情形会被转换为左值引用,第四种会被转换为右值引用。

在上面的例子中,入参为左值引用的情况经折叠后得到:

void func(Widget& param);

至于入参为右值引用的情况,根据上面所述 T 会被推导为 Widget,则实例化结果直接为:

void func(Widget&& param);

恰好为我们所预期的万能引用的表现:入参是什么引用就实例化为什么引用。

  • 至此你可能已经意识到,所谓的“万能引用”,其实不是一种新的引用形式,而是对右值引用在发生了类型推导和引用折叠情况下的表现的概括

  • 引用折叠总共会发生在四种情况中。以上说明的模板实例化是最常见的一种。第二种类似的是 auto 变量类型的推导,仍以一个例子说明:

Widget widgetFactory(); // function returning rvalue
Widget w;               // a variable (an lvalue)auto&& w1 = w;
// w1以左值w初始化,auto被替换为Widget&,得到以下形式
Widget& && w1 = w;
// 经引用折叠,得到w1的最终类型
Widget& w1 = w;auto&& w2 = widgetFactory();
// w2以右值初始化,auto被替换为Widget,直接得到w2的类型
Widget&& w2 = widgetFactory();

第三种情况是 typedef 和别名声明(alias declaration)的生成。引用折叠会消灭在推断一个 typedef 时出现的对引用的引用。例如有以下模板类:

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

如果我们用左值引用类型实例化 Widget 类:

Widget<int&> w;

那么 RvalueRefToT 的推断中会触发折叠:

typedef int& && RvalueRefToT;
↓
typedef int& RvalueRefToT; // 看起来RvalueRefToT的名称不太合适

第四种情况是使用 decltype 时。如果分析 decltype(v)v 的类型时出现了引用的引用,也会触发引用折叠。

  • 了解了引用折叠的机理,就可以理解 std::forward 是如何实现的了。std::forward 的基本代码为(省略了一些与此处无关的细节):
template <class T>
T&& forward(remove_reference_t<T>& param) {return static_cast<T&&>(param);
}

以对万能引用参数调用 std::forward 为例, 当入参为 Widget 的左值时,根据上面的讨论可知 T 被推导为 Widget&,于是调用形式为 std::forward<Widget&>(param),替换并经折叠得到:

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

当入参为 Widget 的右值时,param 仍是左值,但 T 被推导为 Widget,于是调用形式为 std::forward<Widget>(param),替换(不发生折叠)得到:

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

可以看出,两种情况下虽然入参 param 表面上都为左值,但通过附带的 T 的类型信息,实现了对 param “真实类别”的识别,当其为右值时才进行类型转换,左值则什么都不做,这正是 Item 23 中讲到的 std::forward<T>() 的选择性类型转换功能。

有兴趣的读者如果阅读STL源码会发现 std::forward 还有另一种重载形式,其参数类型为右值(remove_reference_t<T>&&)。本例对应的完美转发情形中不会调用该重载,至于其为什么存在,可以参考这个链接的回答。

总结

  1. 引用折叠发生在四种情况中:模板实例化,auto 类型生成,typedef 和 alias declaration 的创建和使用,decltype
  2. 当编译器生成了对引用的引用时,会根据引用折叠规则将其转换为一层引用。只要两层引用中有一个是左值引用,结果就是左值引用,否则是右值引用。
  3. 万能引用的本质是右值引用在发生类型推导和引用折叠现象时的表现的概括。

《Effective Modern C++》学习笔记 - Item 28: 理解引用折叠(reference collapsing)相关推荐

  1. C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward

    实例代码 // 引用折叠,转发.完美转发,forward#include <iostream>using namespace std;template<typename T> ...

  2. 《Effective STL》学习笔记(第一部分)

    本书从STL应用出发,介绍了在项目中应该怎样正确高效的使用STL.本书共有7个小节50个条款,分别为 (1) 容器:占12个条款,主要介绍了所有容器的共同指导法则 (2) vector和string: ...

  3. 《Effective Modern C++》笔记

    文章目录 绪论 第1章 型别推导 条款1:理解模板类型推导 情况1:ParamType 是个指针或引用,但不是万能引用 情况2:ParamType是万能引用 情况3:ParamType既非指针也非引用 ...

  4. Modern C++ 学习笔记——C++函数式编程

    往期精彩: Modern C++ 学习笔记--易用性改进篇 Modern C++ 学习笔记 -- 右值.移动篇 Modern C++ 学习笔记 -- 智能指针篇 Modern C++ 学习笔记 -- ...

  5. 《游戏设计艺术(第2版)》——学习笔记(28)第28章 制作游戏的技术

    <游戏设计艺术(第2版)>学习笔记(28) 第28章 制作游戏的技术 终于该谈论技术了 基础性的和装饰性的 米老鼠的第一部卡通 刺猬索尼克(音速小子) 神秘岛 旅行 布娃娃物理系统(Rag ...

  6. ROS学习笔记六:理解ROS服务和参数

    ROS学习笔记六:理解ROS服务和参数 主要介绍ROS服务和参数,同时使用命令行工具rosservice和rosparam. ROS service service是节点之间互相通信的另一种方式,se ...

  7. ROS学习笔记五:理解ROS topics

    ROS学习笔记五:理解ROS topics 本节主要介绍ROS topics并且使用rostopic和rqt_plot命令行工具. 例子展示 roscore 首先运行roscore系列服务,这是使用R ...

  8. ROS学习笔记四:理解ROS节点

    ROS学习笔记四:理解ROS节点 本节主要介绍ROS图形概念,讨论ROS命令行工具roscore.rosnode和rosrun. 要求 要求已经在Linux系统中安装一个学习用的ros软件包例子: s ...

  9. Effective Modern C++读书笔记

    本笔记主要用于记录要领.体会及摘抄书中精华 第1章 类型推导 1.1 理解模板类型推导 在模板类型推导过程中 具有引用(&)或指针(*)类型的实参会被当成非引用类型来处理.换言之,其引用或指针 ...

最新文章

  1. python官网下载步骤手机-手机python下载
  2. 算法函数:得到一个字符串中的最大长度的数字
  3. csdn在markdown笔记中复制代码格式混乱的解决办法
  4. Zookeeper C API 指南
  5. [css] 一个页面引用多个文件,如何防止样式冲突?
  6. c语言程序设计电加热炉,基于80C52单片机的电加热数字恒温控制系统设计
  7. 框架源码专题:Spring是如何解决循环依赖的?
  8. 《虚无的十字架》—— 读后总结
  9. eclipse环境变量的配置
  10. ActiveMQ下载与安装使用
  11. 判断最小生成树的唯一性
  12. python分位数回归模型_从线性模型到决策树再到深度学习的分位数回归
  13. 查看raid卡型号和固件版本
  14. -bash: unzip: 未找到命令
  15. 精密划片机的三种切割方式
  16. 读《我喜欢生命本来的样子》记(一)
  17. vant框架cdn使用方式的简短案例
  18. 这些行业已经开始用数据挖掘了,我们的前途光明
  19. Word文档中如何打外国人姓名间隔的那一个小点
  20. PTA 7-178 吸血鬼素数

热门文章

  1. 清除浮动的几种方式?各自的优缺点?
  2. android 一键切换夜间模式,实用小工具“月食”:一键切换夜间模式
  3. 朴素贝叶斯为啥叫朴素
  4. 嵌入式电子简单电子相册左右滑动
  5. promise特点与其api的常用方法
  6. PCB开钢网不容忽视的问题
  7. 外星人控制中心(AWCC)出现错误日志导致电脑卡顿的解决建议
  8. 快速了解shell常用内部命令和变量使用
  9. 项目环境搭建【BT Linux面板 7.8.0 】
  10. LibreOJ #2478.「九省联考 2018」林克卡特树 树形dp+带权二分