zoukankan      html  css  js  c++  java
  • C++中的万能引用和完美转发

    本文转发至:C++中的万能引用和完美转发

    1. 阅读这篇博文需要了解C++中的左值(lvalue)和右值(rvalue)的概念,详情参见我的另外一篇博文:C++移动语义及拷贝优化
    2. 万能引用和完美转发多涉及到模板的使用,如若不是自己写模板,则可不用关心

    万能引用(Universal Reference)

    首先,我们来看一个例子:

    #include <iostream>
    using std::cout;
    using std::endl;
    
    template<typename T>
    void func(T& param) {
        cout << param << endl;
    }
    
    int main() {
        int num = 2019;
        func(num);
        return 0;
    }
    

    这样例子的编译输出都没有什么问题,但是如果我们修改成下面的调用方式呢?

    int main() {
        func(2019);
        return 0;
    }
    

    则会得到一个大大的编译错误。因为上面的模板函数只能接受左值或者左值引用(左值一般是有名字的变量,可以取到地址的),我们当然可以重载一个接受右值的模板函数,如下也可以达到效果。

    template<typename T>
    void func(T& param) {
        cout << "传入的是左值" << endl;
    }
    
    template<typename T>
    void func(T&& param) {
        cout << "传入的是右值" << endl;
    }
    
    int main() {
        int num = 2019;
        func(num);
        func(2019);
        return 0;
    }
    

    输出结果:

    传入的是左值传入的是右值
    

    第一次函数调用的是左值得版本,第二次函数调用的是右值版本。但是,有没有办法只写一个模板函数即可以接收左值又可以接收右值呢?

    C++ 11中有万能引用(Universal Reference)的概念:使用T&&类型的形参既能绑定右值,又能绑定左值。

    但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。

    所以,上面的案例我们可以修改为:

    template<typename T>
    void func(T&& param) {
        cout << param << endl;
    }
    
    int main() {
        int num = 2019;
        func(num);
        func(2019);
        return 0;
    }
    

    引用折叠(Universal Collapse)

    万能引用说完了,接着来聊引用折叠(Univers Collapse),因为完美转发(Perfect Forwarding)的概念涉及引用折叠。一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:

    • 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
    • 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
    • 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
    • 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用

    但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:

    所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用

    即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。

    完美转发(Perfect Forwarding)

    下面接着说完美转发(Perfect Forwarding),首先,看一个例子:

    #include <iostream>
    using std::cout;
    using std::endl;
    
    template<typename T>
    void func(T& param) {
        cout << "传入的是左值" << endl;
    }
    
    template<typename T>
    void func(T&& param) {
        cout << "传入的是右值" << endl;
    }
    
    template<typename T>
    void warp(T&& param) {
        func(param);
    }
    
    int main() {
        int num = 2019;
        warp(num);
        warp(2019);
        return 0;
    }
    

    猜一下,上面的输出结果是什么?

    传入的是左值传入的是左值
    

    是不是和我们预期的不一样,下面我们来分析一下原因:

    warp()函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个warp()函数调用实参是左值,所以,warp()函数中调用func()中传入的参数也应该是左值;第二个warp()函数调用实参是右值,根据上面所说的引用折叠规则,warp()函数接收的参数类型是右值引用,那么为什么却调用了调用func()的左值版本了呢?这是因为在warp()函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。

    那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过std::forward()函数来实现。我们修改我们的warp()函数如下:

    template<typename T>
    void warp(T&& param) {
        func(std::forward<T>(param));
    }
    

    则可以输出预期的结果。

  • 相关阅读:
    BZOJ 2260: 商店购物
    BZOJ 4349: 最小树形图
    BZOJ 1115: [POI2009]石子游戏Kam
    BZOJ 1413: [ZJOI2009]取石子游戏
    BZOJ 2275: [Coci2010]HRPA
    BZOJ 4730: Alice和Bob又在玩游戏
    BZOJ 1455: 罗马游戏
    BZOJ 3509: [CodeChef] COUNTARI
    BZOJ 1513: [POI2006]Tet-Tetris 3D
    #大数加减乘除#校赛D题solve
  • 原文地址:https://www.cnblogs.com/jmliao/p/14327117.html
Copyright © 2011-2022 走看看