zoukankan      html  css  js  c++  java
  • C++11 完美转发

    【1】为什么引入完美转发?

    在函数模板编程中,常有一种场景是把模板参数转发给另一个调用函数,这时候如果只提供值传递版本会显得效率太低。看以下代码:

    1 template<class TYPE, class ARG>
    2 TYPE* getInstance(ARG arg)
    3 {
    4     TYPE* pRet = nullptr;
    5     pRet = new TYPE(arg);
    6     return pRet;
    7 }

    代码很简单,就是用ARG参数去初始化一个TYPE类型的对象,然后返回该对象指针。

    考虑一下,如果ARG类型是一个自定义类型,那么这样的值传递会是比较大的性能开销。

    有没有办法改进一下?再看以下代码:

    1 template<class TYPE, class ARG>
    2 TYPE* getInstance(const ARG& arg)
    3 {
    4     TYPE* pRet = nullptr;
    5     pRet = new TYPE(arg);
    6     return pRet;
    7 }

    这段代码将传入参数类型改为了“万能”的常量左值引用,可以接受任何类型,可以解决性能开销的问题。

    但是,但是,还不够灵活,假如我们想TYPE接受一个右值去初始化呢?

    那么有没有可以把参数连同类型一起转发的方案呢?当然有,没有C++办不到的。

    C++11就提供了这样能力既完美转发。代码如下:

    1 template<class TYPE, class ARG>
    2 TYPE* getInstance(ARG&& arg)
    3 {
    4     TYPE* pRet = nullptr;
    5     pRet = new TYPE(std::forward<ARG>(arg));
    6     return pRet;
    7 }

    嗯哼?难道把形参类型改为右值引用就可以了?懵圈....表示颠覆认知!看不懂无所谓,再往下看。

    【2】引用折叠 与 模板推导

    C++11是通过引入一条所谓“引用折叠”(reference collapsing)的新语言规则,并结合新的模板推导规则来实现的完美转发。

    先了解一下引用折叠。如下语句:

    1 typedef const int T;
    2 typedef T& TR;
    3 TR& v = 10; // 该声明在C++98中会导致编译错误 

    在C++11之前,其中TR& v = 10;这样的表达式会被编译器认为是不合法的表达式。

    而在C++11中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式,

    具体规则,如下图所示:

    个人觉得,这个规则还是得深刻理解,记住一个原则:一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。

    如下验证程序:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 typedef const int T;
     5 typedef T& TR;
     6 typedef T&& TRR;
     7 
     8 void JudgeType()
     9 {
    10     cout << "lvalue_ref_type?: " << is_lvalue_reference<TR>::value << endl;  // 1
    11     cout << "rvalue_ref_type?: " << is_rvalue_reference<TR>::value << endl;  // 0
    12 
    13     cout << "lvalue_ref_type?: " << is_lvalue_reference<TR&>::value << endl; // 1
    14     cout << "rvalue_ref_type?: " << is_rvalue_reference<TR&>::value << endl; // 0
    15 
    16     cout << "lvalue_ref_type?: " << is_lvalue_reference<TR&&>::value << endl; // 1
    17     cout << "rvalue_ref_type?: " << is_rvalue_reference<TR&&>::value << endl; // 0
    18 
    19     cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR>::value << endl;  // 0
    20     cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR>::value << endl;  // 1
    21 
    22     cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR&>::value << endl; // 1
    23     cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR&>::value << endl; // 0
    24 
    25     cout << "lvalue_ref_type?: " << is_lvalue_reference<TRR&&>::value << endl; // 0
    26     cout << "rvalue_ref_type?: " << is_rvalue_reference<TRR&&>::value << endl; // 1
    27 }
    28 
    29 int main()
    30 {
    31     JudgeType();
    32     system("pause");
    33 }

    模板推导。为了便于说明问题,下面我们把函数模板写为如下形式:

    1 template <typename T>
    2 void IamForwording(T&& t)
    3 { 
    4     IrunCodeActually(static_cast<T&&>(t));
    5 }

    说明:IamForwording为转发函数;IrunCodeActually为目标函数(即真正执行函数过程的目标)

    模板对类型的推导规则就比较简单:

    当转发函数的实参是类型X的一个左值引用,那么模板参数被推导为X&类型;

    当转发函数的实参是类型X的一个右值引用,那么模板的参数被推导为X&&类型。

    再结合以上的引用折叠规则,就能确定出参数的实际类型。

    尤其注意,我们不仅在参数部分使用了T&&这样的标识,在目标函数传参的强制类型转换中也使用了这样的形式。

    这个标识不是右值引用,它有专用的名字为转发引用(forwarding reference)。

    比如,我们调用转发函数时传入了一个X类型的左值引用,可以想象,转发函数将被实例化为如下形式:

    1 void IamForwording(X& && t) 
    2 { 
    3     IrunCodeActually(static_cast<X& &&>(t)); 
    4 }

    应用上引用折叠规则,就是:

    1 void IamForwording(X& t)
    2 {
    3     IrunCodeActually(static_cast<X&>(t));
    4 }

    如此一来,左值传递就毫无问题了。

    实际使用的时候,IrunCodeActually如果接受左值引用的话,就可以直接调用转发函数。

    不过你可能会发现,这里调用前的static_cast没有什么作用。

    事实上,这里的static_cast是留给传递右值用的。如下分析。

    如果我们调用转发函数时传入了一个X类型的右值引用的话,我们的转发函数将被实例化为:

    1 void IamForwording(X&& && t)
    2 {
    3     IrunCodeActually(static_cast<X&& &&>(t));
    4 }

    应用上引用折叠规则,就是:

    1 void IamForwording(X&& t)
    2 {
    3     IrunCodeActually(static_cast<X&&>(t));
    4 }

    这里我们就看到了static_cast的重要性。

    事实上,对于一个右值而言,当它使用右值引用表达式引用的时候,该右值引用却是个不折不扣的左值。

    那么我们想在函数调用中继续传递右值,就需要使用std::move来进行左值向右值的转换。

    而std::move通常就是一个static_cast。不过在C++11中,用于完美转发的函数却不再叫作move,而是另外一个名字:forward。

     move和forward在实际实现上差别并不大。

    不过标准库这么设计,也许是为了让每个名字对应于不同的用途,以应对未来可能的扩展。

    推荐在实现完美转发时使用forward。

    【3】完美转发的应用

    完美转发的应用示例:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 void fun(int& x) { cout << "call lvalue ref" << endl; }
     5 void fun(int&& x) { cout << "call rvalue ref" << endl; }
     6 void fun(const int& x) { cout << "call const lvalue ref" << endl; }
     7 void fun(const int&& x) { cout << "call const rvalue ref" << endl; }
     8 
     9 template<typename T>
    10 void PerfectForward(T&& t)
    11 {
    12     std::cout << "T is a ref type?: " << std::is_reference<T>::value << std::endl;
    13     std::cout << "T is a lvalue ref type?: " << std::is_lvalue_reference<T>::value << std::endl;
    14     std::cout << "T is a rvalue ref type?: " << std::is_rvalue_reference<T>::value << std::endl;
    15 
    16     fun(forward<T>(t));
    17 }
    18 
    19 int main()
    20 {
    21     PerfectForward(10);           // call rvalue ref
    22 
    23     int a = 5;
    24     PerfectForward(a);            // call lvalue ref
    25     PerfectForward(move(a));      // call rvalue ref
    26 
    27     const int b = 8;
    28     PerfectForward(b);           // call const lvalue ref
    29     PerfectForward(move(b));     // call const rvalue ref
    30 
    31     system("pause");
    32     return 0;
    33 }
    34 
    35 /*
    36 T is a ref type?: 0
    37 T is a lvalue ref type?: 0
    38 T is a rvalue ref type?: 0
    39 call rvalue ref
    40 T is a ref type?: 1
    41 T is a lvalue ref type?: 1
    42 T is a rvalue ref type?: 0
    43 call lvalue ref
    44 T is a ref type?: 0
    45 T is a lvalue ref type?: 0
    46 T is a rvalue ref type?: 0
    47 call rvalue ref
    48 T is a ref type?: 1
    49 T is a lvalue ref type?: 1
    50 T is a rvalue ref type?: 0
    51 call const lvalue ref
    52 T is a ref type?: 0
    53 T is a lvalue ref type?: 0
    54 T is a rvalue ref type?: 0
    55 call const rvalue ref
    56 */

    所有4种类型的值对完美转发进行测试,可以看到,所有的转发都被正确地送到了目的地。

    注意分析,加深理解。

    good good study, day day up.

    顺序 选择 循环 总结

  • 相关阅读:
    字符编码笔记:ASCII,Unicode 和 UTF-8
    nginx 负载均衡设置
    ubuntu 修改时区
    js 高阶函数 filter
    js 高阶函数 map reduce
    省市联级菜单--js+html
    php代码优化技巧
    json、xml ---- 数据格式生成类
    初识设计模式(1)---单例、工厂、注册树
    php 链式操作的实现 学习记录
  • 原文地址:https://www.cnblogs.com/Braveliu/p/12235618.html
Copyright © 2011-2022 走看看