[C++中级进阶]001_C++0x里的完美转发到底是神马?
[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
[C++中级进阶]001_C++0x里的完美转发到底是神马?的更多相关文章
- 无需Flash录视频——HTML5中级进阶
前言 HTML5的权限越来越大了,浏览器可以直接调用摄像头.麦克风了,好激动啊.我们要用纯洁的HTML代码造出自己的天地. 视频采集 本篇介绍的栗子 都是在chrome 47 版本以上的,低版本的可能 ...
- C++ 左值与右值 右值引用 引用折叠 => 完美转发
左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...
- 完美转发(perfect forwarding)、universal reference、引用折叠(collasping)
首先要分清: C++里的值只有两种值:左值.右值.—— 其本质应该是内存中存储的值/instance分两种:一种是持久的,一种是“短暂的” 也只有两种引用: 左值引用.右值引用. ——引用,就是这个内 ...
- Effective Modern C++:05右值引用、移动语义和完美转发
移动语义使得编译器得以使用成本较低的移动操作,来代替成本较高的复制操作:完美转发使得人们可以撰写接收任意实参的函数模板,并将其转发到目标函数,目标函数会接收到与转发函数所接收到的完全相同的实参.右值引 ...
- C++11 左值引用和右值引用与引用折叠和完美转发
1.左值与右值 最感性的认识. 当然,左值也是可以在右边的. 左值是可以被修改的,右值不能. 当然取地址也是. 生存周期一般左值会比右值的长,一般右值都计算时产生的无名临时对象,存在时间比较短. 下面 ...
- C++11 变长模版和完美转发实例代码
C++11 变长模版和完美转发实例代码 #include <memory>#include <iostream>#include <vector>#include ...
- SQLSERVER 里经常看到的CACHE STORES是神马东东?
SQLSERVER 里经常看到的CACHE STORES是神马东东? 当我们在SSMS里执行下面的SQL语句清空SQLSERVER的缓存的时候,我们会在SQL ERRORLOG里看到一些信息 DBCC ...
- 翻译「C++ Rvalue References Explained」C++右值引用详解 Part8:Perfect Forwarding(完美转发):解决方案
本文为第八部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...
- 第16课 右值引用(3)_std::forward与完美转发
1. std::forward原型 template <typename T> T&& forward(typename std::remove_reference< ...
随机推荐
- PHP data
- Android进程绝杀技--forceStop
一.概述 1.1 引言 话说Android开源系统拥有着App不计其数,百家争鸣,都想在这"大争之世"寻得系统存活的一席之地.然则系统资源有限,如若都割据为王,再强劲的CPU也会忙 ...
- js 刷新窗口
在js 方法里面 1.window.opener.location.reload() 刷新父窗口 2.window.location.reload() 该方法强迫浏览器刷新当前 ...
- 联合体union和大小端(big-endian、little-endian)
1.联合体union的基本特性——和struct的同与不同 union,中文名“联合体.共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以 ...
- biweb添加新的模块
1.例如添加一个新闻模块,首先去纯净的项目的根目录复制出一个news文件夹到项目外 2.打开dreamweaver, 编辑 ->查找和替换 例如 新模块 叫 我的新闻,英文名叫mynews,则进 ...
- web前端页面项目经验总结
项目时间:2016年4月5日--4月9日项目名称:阿七果子园web前端页面项目内容: 1.HTML5+CSS+JavaScript(banner+timer)+JQuery(small_bann ...
- 第一章 UI实战开发 UIWindow UIView
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- javascript和jquey的自定义事件小结
“通过事件机制,可以将类设计为独立的模块,通过事件对外通信,提高了程序的开发效率.” 可以把多个关联但逻辑复杂的操作利用自定义事件的机制灵活地控制好 对象之间通过直接方法调用来交互 1)对象A直接调用 ...
- Mongodb 副本集分片(一)---初始化mongodb安装启动
写在前面:mongodb是nosql非关系型数据库中,比较受欢迎的产品.在数据持久化及与关系型数据库的关联上也做的比较好,目前各大公司在存放二进制文件(图片.视频等)中应用也比较广泛.其遵循的key- ...
- 矩阵k次幂 采用三重循环
#include<iostream> using namespace std; int main() { int n,k; ][],b[][],c[][]; while(cin>&g ...