[C++中级进阶]001_C++0x里的完美转发到底是神马?

转载至:http://www.cnblogs.com/alephsoul-alephsoul/archive/2013/01/10/2853900.html

问题描述

C++无疑是十分强大的,但是你可知道,在C++0x标准出现之前,在C++界里有一个十分棘手而未能解决的问题——参数转发。问题的描述如下:

对于一个给定的函数E(a1, a2, ..., an),它有参数a1, a2, ..., an,你不可能写出一个函数F(a1, a2, ..., an),使得该函数与E(a1, a2, ..., an)完全等价。

对这个问题进而拆分,它有两点:第一,函数F(a1, a2, ..., an)比如能够接受任意的参数列表,并在不改变参数性质的前提下,将参数传递给E(a1, a2, ..., an);第二,函数F(a1, a2, ..., an)必须能够将函数E(a1, a2, ..., an)的结果返回给自己的调用者。

本文就第一点的参数转发进行讲解。

标准解决方案的三大规则

从更为严密的逻辑角度上来考虑转发的问题,我们的转发实现需要满足下面三个条件。这里假设函数F(a1, a2, ..., an)调用函数G(a1, a2, ..., an)

C1. 对于能使用函数F(a1, a2, ..., an)的地方,函数G(a1, a2, ..., an)也一定能使用。

C2. 对于不能使用函数F(a1, a2, ..., an)的地方,函数G(a1, a2, ..., an)也一定不能使用。

C3. 实现函数F(a1, a2, ..., an)的时候,复杂度必须是线性增加的。(这一点乍看起来可能不理解,现在可以不用理解,后面有实例说明)

七种转发实现方案

在实现转发的方案里,有七种需要我们了解,本文会为你一一介绍,这七种转发里,只有第七种能实现完美转发。

七种转发中,1-4不需要对C++0x以前的标准进行修改,第五种转发需要对标准关于参数推倒的规则进行修改,第六种和第七种都用到了C++0x标准里的右值引用。

方案1. 非常量左值引用转发

何谓非常量左值引用呢?形如int& a;我们就把a叫做非常量左值引用。这里的左值引用就是我们平时使用的&。

这个解决方案的实例代码如下:

1 template<class A1, class A2, class A3>
2 void f(A1 & a1, A2 & a2, A3 & a3)
3 {
4     return g(a1, a2, a3);
5 }
6
7 void g(int a1, int a2, int a3){
8
9 }

转发失败原因:这个转发不能传入非常量右值,即下面的代码是无法通过编译的。

1 int main()
2 {
3     f(1, 2, 3);
4 }

但是这种解决方案并不是一无是处的,对于那些只可能传入左值的场景来说,比如Boost库中的Iterator,这种应用还是能够看到的。只是它并不是一个完美的转发方案。

方案2. 常量左值引用转发

何谓常量左值引用呢?形如const int& a;我们就把a叫做常量左值引用。

这个解决方案的实例代码如下:

1 template<class A1, class A2, class A3>
2 void f(A1 const & a1, A2 const & a2, A3 const & a3)
3 {
4     return g(a1, a2, a3);
5 }
6
7 void g(int& a1, int& a2, int& a3){
8
9 }

转发失败原因:上面的函数f虽然可以接受任何参数列表,但是当函数g接受非常量左值引用变量时,函数f是无法将常量左值引用参数传递给非常量左值引用的。

这个解决方案一般用于拷贝构造函数,因为拷贝构造函数一般传递的都是形如const A&的参数,但也不排除有的拷贝构造函数比较变态,不传递const A&的参数。

方案3. 非常量左值引用+常量左值引用转发

这个解决方案的实例代码如下:

 1 template<class A1> 2 void f(A1 & a1)3 {4     return g(a1);5 }6 7 template<class A1> 8 void f(A1 const & a1)9 {
10     return g(a1);
11 }

转发失败原因:上面的实现的确可以满足所有的参数都能传递了,并且当函数g接受非常量参数时,编译器也能找到最佳的匹配模板函数,即第一个。当然这个前提是所有的编译器达成共识,认定第一个模板函数。除此之外,还有一个重要的问题,当函数的参数有三个的时候,我们不得不像下面这样来实现我们的函数f:

 1 template<class A1, class A2, class A3> 2 void f(A1 const & a1, A2 const & a2, A3 const & a3)3 {4     return g(a1, a2, a3);5 }6 7 template<class A1, class A2, class A3> 8 void f(A1 & a1, A2 const & a2, A3 const & a3)9 {
10     return g(a1, a2, a3);
11 }
12
13 template<class A1, class A2, class A3>
14 void f(A1 const & a1, A2 & a2, A3 const & a3)
15 {
16     return g(a1, a2, a3);
17 }
18
19 template<class A1, class A2, class A3>
20 void f(A1 & a1, A2 & a2, A3 const & a3)
21 {
22     return g(a1, a2, a3);
23 }
24
25 template<class A1, class A2, class A3>
26 void f(A1 const & a1, A2 const & a2, A3 & a3)
27 {
28     return g(a1, a2, a3);
29 }
30
31 template<class A1, class A2, class A3>
32 void f(A1 & a1, A2 const & a2, A3 & a3)
33 {
34     return g(a1, a2, a3);
35 }
36
37 template<class A1, class A2, class A3>
38 void f(A1 const & a1, A2 & a2, A3 & a3)
39 {
40     return g(a1, a2, a3);
41 }
42
43 template<class A1, class A2, class A3>
44 void f(A1 & a1, A2 & a2, A3 & a3)
45 {
46     return g(a1, a2, a3);
47 }

是的,这是指数级别的增长,这个就不符合我们的C3规则了,呵呵,这下理解C3规则是什么含义了吧。

方案4. 常量左值引用+const_cast转发

const_cast的作用是什么呢?它可以去除常量的的const属性,这个转换可以解决方案2里遇到的问题。

这个解决方案的实例代码如下:

1 template<class A1, class A2, class A3>
2 void f(A1 const & a1, A2 const & a2, A3 const & a3)
3 {
4     return g(const_cast<A1 &>(a1), const_cast<A2 &>(a2), const_cast<A3 &>(a3));
5 }

转发失败原因:很显然,去除了const属性,我们就能修改原来的常量了,这样的转发会造成对常量的修改。这里让我十分郁闷的是,C++既然提供了const,还非得提供一个const_cast,这是矛盾的存在。或许这就是C++的牛逼之处吧,哈哈。

方案5. 非常量左值引用+修改的参数推倒规则转发

这里说明一下,我在看胡健的博客的时候,对“修改的参数推倒”这句话费透了脑筋。乍一看不懂,仔细看还是不懂,基础不扎实可能吧。不过最后还是搞懂了,这里所谓的“修改参数推倒”是指修改C++的现有标准。在模板编程里,有一种参数推倒的说法。当你传递int类型的参数时,编译器会为你找到最佳匹配的模板函数,然后再把参数传递给这个模板函数。在方案1里,导致我们失败的事情就是无法传递非常量右值,但是如果修改C++标准,我们就能够将非常量右值推倒成常量右值。

但是,修改标准后,对于现有用C++实现的代码造成十分巨大的破坏。具体参照如下代码:

 1 template<class A1> 2 void f(A1 & a1)3 {4     std::cout << 1 << std::endl;5 }6 7 void f(long const &)8 {9     std::cout << 2 << std::endl;
10 }
11
12 int main()
13 {
14     f(5);              // 在既有参数推倒规则,会打印2;修改参数推倒规则后,会打印1
15     int const n(5);
16     f(n);              // 这种情况好一点,都会打印1
17 }

看出来对现有代码的破坏了吗?注意注释。

方案6. 右值引用转发

何谓右值引用呢?这是C++0x里新追加的特性,如果想更清楚一点,可以参考胡健的博客。

这个解决方案的实例代码如下:

1 template<class A1, class A2, class A3>
2 void f(A1 && a1, A2 && a2, A3 && a3)
3 {
4     return g(a1, a2, a3);
5 }

转发失败原因:函数g无法接收左值,因为不能将一个左值传递给一个右值引用。另外,当传递非常量右值时也会存在问题,因为此时a1、a2、a3本身是左值,这样当F的参数是非常量左值引用时,我们就可以来修改传入的非常量右值了,而右值是不能被修改的。

完美方案7. 右值引用+修改的参数推倒规则转发

你可能疑惑,不是说过修改参数推倒规则后会导致对既有代码的破坏吗?是的,不过那是对左值参数推倒规则的修改,我们这里要修改的是针对右值引用推倒规则的修改。首先,要理解参数推倒规则,我们要理解引用叠加规则:

1、T& + &         = T&

2、T& + &&       = T&

3、T&& + &       = T&

4、T或T&& + && = T&&

如何验证上面的引用叠加规则呢?我们可以用下面这样一段代码来验证这个问题:

 1 #include <iostream>2 using namespace std;3 4 typedef int&  LRINT;5 typedef int&& RRINT;6 7 int main(){8 9     int     a = 10;
10
11     // 左值引用
12     LRINT   b = a;      // 单纯:&
13     LRINT&  c = a;      // 叠加:&  +  &   不能写做:LRINT&  c = 10;    可见c是左值引用
14
15     // 右值引用
16     RRINT    d = 10;    // 单纯:&&
17     RRINT&&  e = 10;    // 叠加:&& + &&   不能写做:RRINT&& e = a;     可见e是右值引用
18     LRINT&&  f = a;     // 叠加:&  + &&   不能写做:LRINT&&  f = 10;   可见f是左值引用
19     RRINT&   g = a;     // 叠加:&& +  &   不能写做:RRINT&   g = 10;   可见g是左值引用
20
21     system("pause");
22     return 0;
23 }

理解了引用叠加规则后,让我们来看看修改后的针对右值引用的参数推倒规则:

修改后的针对右值引用的参数推导规则为:若函数模板的模板参数为A,模板函数的形参为A&&,则可分为两种情况讨论:

1、若实参为T&,则模板参数A应被推导为引用类型T&。(由引用叠加规则第2点T& + && = T&和A&&=T&,可得出A=T&)

2、若实参为T&&,则模板参数A应被推导为非引用类型T。(由引用叠加规则第4点T或T&& + && = T&&和A&&=T&&,可得出A=T或T&&,强制规定A=T)

应用了新的参数推导规则后,我们来看下面的代码:

1 template<class A1>
2 void f(A1 && a1)
3 {
4     return g(static_cast<A1 &&>(a1));
5 }

当传给f一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。这样,在f内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T& &&>(a),根据引用叠加规则第2点,即为static_cast<T&>(a),这样转发给g的还是一个左值。

当传给f一个右值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为右值引用类型T&&,根据推导规则2,模板参数A被推导为T。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T&&>(a),这样转发给F的还是一个右值(不具名右值引用是右值)。

可见,使用该方案后,左值和右值都能正确地进行转发,并且不会带来其他问题。

参考资料

1.【原】C++ 11完美转发   http://www.cnblogs.com/hujian/archive/2012/02/17/2355207.html

2.【原】C++ 11右值引用   http://www.cnblogs.com/hujian/archive/2012/02/13/2348621.html

3. The Forwarding Problem: Arguments  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm

4. A Proposal to Add an Rvalue Reference to the C++ Language  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html

5. A Brief Introduction to Rvalue References  http://www.artima.com/cppsource/rvalue.html

转载于:https://www.cnblogs.com/GuiltySpark/p/5728954.html

[C++中级进阶]001_C++0x里的完美转发到底是神马?相关推荐

  1. 使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器

    使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器 实现功能 C++实现代码 实现功能 使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器 C+ ...

  2. Revit教程免费下载——Revit建筑设计中级进阶视频课程

    Revit建筑设计中级进阶视频课程,总共有四个章节.附带课程配套的素材文件! [下载地址] 链接:https://pan.baidu.com/s/1-4jrDaVgpwHV9zt8KWzOjg 提取码 ...

  3. 多屏互动——H5中级进阶

    声明:本文CSDN作者原创投稿文章,未经许可禁止任何形式的转载. 作者:王诗诗,前端新人,专职前端工作两年.曾供职于AMI做底层软件开发.喜欢分析H5代码,追崇用简单的CSS,构建精美动效,做前端之前 ...

  4. 多屏互动——H5中级进阶 - 修复动态图片版本

    前言 随着智能硬件的普及,手机,平板,PC甚至路边的电子广告牌,现代浏览器已经无处不在.在浏览器里编织出我们自己的一片天地已经轻车熟路,但是这还不够,H5赋予了浏览器太多的新特性,等待我们去使用.这篇 ...

  5. [C++基础]039_C++异常处理初级出门+中级进阶

    [C++基础]039_C++异常处理初级出门+中级进阶 参考文章: (1)[C++基础]039_C++异常处理初级出门+中级进阶 (2)https://www.cnblogs.com/alephsou ...

  6. SAP UI5 进阶 - XML 视图里定义的 UI 控件,运行时实例化的技术细节剖析试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 作者简介 Jerry Wang,2007 年从电子科技大学计算机专业硕士毕业后加入 SAP 成都研究院工作至今.Jerry 是 SAP 社区导师,S ...

  7. Office VBA开发经典-中级进阶卷 配套资源下载

    <Office VBA开发经典-中级进阶卷>源代码下载 提取码:nt1h 如果遇到下载链接失效,请联系作者进行更新. 开发资源(编程过程中用到的工具.软件):加QQ群61840693咨询, ...

  8. gis data editor.php,我们的漏洞Webug 3.0中级进阶攻略(上)

    WeBug名称定义为"我们的漏洞"靶场环境.基础环境是基于PHP/mysql制作搭建而成,中级环境与高级环境分别都是由互联网漏洞事件而收集的漏洞存在的操作环境.部分漏洞是基于Win ...

  9. std:forward 完美转发

    概述:     // TEMPLATE CLASS identity template<class _Ty>     struct identity     {    // map _Ty ...

最新文章

  1. 插入记录时单引号的处理
  2. python入门教程非常详细-python初学者怎么入门:python入门教程非常详细
  3. 挖洞经验 | 看我如何发现“小火车托马斯”智能玩具APP聊天应用漏洞
  4. 读取一个文件中的字符,统计每个字符出现的次数
  5. Matlab--m代码转C与C++代码)2(详尽示例与描述)
  6. 伪静态php空间,Win空间上如何实现WordPress博客的完美伪静态
  7. 另类网页设计:30个复古怀旧风格的网站作品
  8. iphone之使用讯飞语音sdk实现语音识别功能
  9. centos7下学习Redis(一)
  10. 推免生是否抢了考研生的“奶酪”
  11. AI数学基础(1)--- 马尔可夫不等式
  12. vue 使用iframe展示pdf文件
  13. matlab表达式中的省略号,使用正则表达式匹配省略号
  14. dac芯片(国产dac芯片)
  15. 专科咋了?5年时间从三流外包到阿里P6,逆袭成功终于肝出了这份大厂Android研发岗中高级面经!
  16. iOS 高德地图(二)(进阶具体使用的细节)
  17. JavaWEB-04 项目案例(1)
  18. 半年萌新对BUG分析和理解
  19. Chisel入门------Chisel的基本语法4
  20. 数据结构第六章图的思维导图

热门文章

  1. STM32 USB IAP升级
  2. 达观OCR,图像文字抽取算法平台满足业务场景快速定制
  3. 傅里叶变换公式的物理意义
  4. 300大作战这款国风动漫二次元游戏如何?
  5. vue 前端导入excel文件,并在页面表格展示
  6. CAP(AP模式/CP模式)
  7. Linux下的截图工具 —— Spectable
  8. python钢琴块自动脚本
  9. 解决烦人的sudo报错问题
  10. 2022年博客之星排行榜 日榜 2023-01-02 博客之星总榜