美文网首页
C++11新特性--完美转发

C++11新特性--完美转发

作者: 于天佐 | 来源:发表于2018-02-02 19:05 被阅读0次

    转发的问题

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

    template<class TYPE, class ARG>
    TYPE* get_instance(ARG arg)
    {
         TYPE* ret;
         ret = new TYPE(arg);
         return ret;
    }
    

        代码很简单,就是用ARG参数去初始化一个TYPE类型的对象,然后返回该对象指针。考虑一下如果ARG类型是一个自定义类型,那么这样的值传递会是比较大的性能开销。有没有办法改进一下?看一下代码

    template<class TYPE, class ARG>
    TYPE* get_instance(const ARG& arg)
    {
         TYPE* ret;
         ret = new TYPE(arg);
         return ret;
    }
    

        这段代码将传入改为了万能的常量左值引用,可以接受任何类型,可以解决性能开销的问题,但是不够灵活,如果我们想TYPE接受一个右值去初始化呢?那么有没有可以把参数连同类型一起转发的方案呢?C++11提供了这样能力既完美转发。代码如下

    template<class TYPE, class ARG>
    TYPE* get_instance(ARG&& arg)
    {
         TYPE* ret;
         ret = new TYPE(std::forward<ARG>(arg));
         return ret;
    }
    

    形参改为了右值引用么?

    两个模板函数推导原则

        解释一下前面留下的问题。在模板函数中(注意是模板函数不是模板类,也不是普通函数,只是模板函数),形如上例中的形参即T&&,如果T是一个推导类型(即模板参数)它的意思并不是右值引用,它有一个专用的名字forwarding reference,它遵循以下的特殊推导规则来推导T的类型:
    1、引用折叠原则(reference collapsing rule),下面以int为类型举例,以区别于模板参数T
    a)int& & (引用的引用) 被转化成 int&
    b)int&& & (rvalue的引用)被传化成 int&
    c)int& && (引用作rvalue) 被转化成 int&
    d)int&& && 被转化成 int&&
    2、推导原则
    以上面模板函数为例,三个特殊之处(左值引用,常量左值引用如预期被推导出来)
    a)如果传入get_instance的参数是int类型(即左值),那么ARG会被推导为int&,即ARG=int&
    b)如果传入get_instance的参数是右值int类型(即右值),那么ARG被推导为int,即ARG=int
    c)如果传入get_instance的参数是const int类型(即常量左值),那么ARG被推导为const int&,即ARG=const int&

    注意:为什么这里没有参数类型是右值引用类型的参数??
    其实右值引用是一个左值,即你传入函数的实参类型有可能是一个左值(左值引用,左值,常量左值引用,右值引用),也有可能是一个右值。注意到细微的区别了么?这里本章最后会给出一些代码示例。

    以上三个特殊推导之处

    结合std::forward代码来看一下

    template <class _Tp>
    inline
    _Tp&&
    forward(typename remove_reference<_Tp>::type& __t)
    {
        return static_cast<_Tp&&>(__t);
    }
    
    template<class TYPE, class ARG>
    TYPE* get_instance(ARG&& arg)
    {
         TYPE* ret;
         ret = new TYPE(std::forward<ARG>(arg));
         return ret;
    }
    

    现在结合上面两个原则来推导一下:
    a)调用get_instance传入int类型,ARG推导为int&,传递给std::forward的模板参数就是int&类型,std::forward中的typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int& &&即int&,最后forward转发为int&类型,完美转发。
    b)调用get_instance传入int右值类型,ARG推导为int,传递给std::forward的模板参数就是int类型,std::forward中的typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int&&即int&&,最后forward转发为int&&类型,完美转发。
    c)调用get_instance传入const int类型,ARG推导为const int&,传递给std::forward的模板参数就是const int&类型,std::forward中的typename remove_reference<_Tp>::type为const int类型,static_cast中_Tp&&类型根据引用折叠,推导为const int& &&即const int&,最后forward转发为const int&类型,完美转发。
    d)调用get_instance传入int&类型,ARG推导为int&,传递给std::forward的模板参数就是int&类型,std::forward中的typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为int& &&即int&,最后forward转发为int&类型,完美转发。
    e)调用get_instance传入const int&类型,ARG推导为const int&,传递给std::forward的模板参数就是const int&类型,std::forward中的typename remove_reference<_Tp>::type为int类型,static_cast中_Tp&&类型根据引用折叠,推导为const int& &&即const int&,最后forward转发为const int&类型,完美转发。

    完美转发

        上面分析的情况即为完美转发,总结一下是模板函数形参形如T&&,结合库函数std::forward来将参数实现类型的完美转发。

    代码示例

        用代码来补充说明一下上面留下的问题。

    template <typename T>
    void
    wrapper(T&& value)
    {
        std::cout<<"T is a ref type(lvalue or rvalue ref)?:"<<std::is_reference<T>::value<<std::endl;
        std::cout<<"T is right value ref?:"<<std::is_rvalue_reference<T>::value<<std::endl; //none T is deduce to rvalue ref
    }
    static void execute()
    {
        int left = 1;
        int &&right = 2;
    
        wrapper(right); //input is a lvalue
        wrapper(3); //intput is a rvalue
        wrapper(std::move(left)); //input is a rvalue
        wrapper(left); //input is a lvalue
    }
    

    输出是
    T is a ref type(lvalue or rvalue ref)?:1
    T is right value ref?:0
    T is a ref type(lvalue or rvalue ref)?:0
    T is right value ref?:0
    T is a ref type(lvalue or rvalue ref)?:0
    T is right value ref?:0
    T is a ref type(lvalue or rvalue ref)?:1
    T is right value ref?:0

    结果跟我们上述的分析是一致的,T不会推导为右值引用,原因是输入参数只有左值和右值两种,不存在右值引用这种输入。
    简单分析一下前三个wrapper调用的区别,第一句调用right是个右值引用类型的变量,自然可以被取地址,所以它是一个左值。
    第二句调用毋庸置疑传入的是一个右值。
    第三句调用跟第二句的效果一模一样,由于std::move返回了一个右值引用类型的返回值,函数返回值通常是个右值,所以这里它的返回值自然是一个右值。

    最后几点值得注意的

        再对比一下完美转发和接受一个右值引用的函数对比。

    template <typename T>
    void
    wrapper(T&& value);
    
    void 
    wrapper_none_template(int&& value);
    

        这两个函数的区别是,上面的模板函数即提现c++11所谓的完美转发,T会根据传入实参参数的不同(左值、右值、左值引用、常量左值引用)而推导出不同的类型。
    下面的函数是一个只接受右值实参的函数,如果传入的值不是右值,那么编译会报错。
        还有一点,用模板函数forwarding reference去进行转发,所有的传入参数最终都是引用类型(左值的和右值的)即不产生值传递所造成的额外构造和析构。之所以提到这一点,主要是针对传入一个右值的情况。前面提到如果传入一个右值int,T会被推导为int,但是形参还是int&&,也就是说形参是一个右值引用,它绑定了一个右值,所以还是一个引用。

    相关文章

      网友评论

          本文标题:C++11新特性--完美转发

          本文链接:https://www.haomeiwen.com/subject/ilxezxtx.html