• C++的万能引用解析


    C++11除了带来了右值引用以外,还引入了一种称为“万能引用”的语法;通过“万能引用”,对某型别的引用T&&,既可以表达右值引用,也可以表达左值引用。

    定义

    该语法有两种使用场景,最常见的一种是作为函数模板的形参:

    template<typename T>
    void f(T&& param);
    

    其中param就是一个万能引用。
    第二个场景则是auto声明:

    auto&& var2 = var1;
    

    这两种情况都涉及到了型别的推导,也就是说,如果你虽然遇到了T&&的形式,但是不涉及型别推导,那么它只是一个右值引用:

    void f(Widget&& param);
    

    同时,想要成为万能引用,变量声明的形式也必须正确无误,必须正好是形如“T&&”才行。比如下面这些情况就不是万能引用:

    template<typename T>
    void f1(std::vector<T>&& param); // param是一个右值引用
    
    template<typename T>
    void f2(const T&& param);      //param也是一个右值引用
    

    如果你向f1和f2传左值,会都是会直接报错的。
    除此之外,像下面这种情况也不是万能引用:

    template<class T, class Allocator=allocator<T>>
    class vector{
    public:
      void push_back(T&& x);
    };
    

    push_back的形参x的型别T是受vector影响的,假设给定T为Widget,那么就会被实例化为如下代码:

    template<class Widget, class Allocator=allocator<T>>
    class vector{
    public:
      void push_back(Widget&& x);
    };
    

    而作为对比,vector类中的emplace_back函数则是一个实实在在的万能引用:

    template<class T, class Allocator=allocator<T>>
    class vector{
    public:
      template<class...Args>
      void emplace_back(Args&&.. args);
    };
    

    args的型别Args完全独立于vector的型别形参T,Args必须在每次调用emplace_back时被推导,因此args是万能引用。

    为什么是“万能引用”?

    你一定非常好奇,为什么这种形态被称作“万能引用”。原因正像前文所说的,通过“万能引用”,对某型别的引用T&&,既可以表达右值引用,也可以表达左值引用。
    比如说,传入一个右值引用,一般都要给传入的参数加一个std::move操作确保变量的可移动性:

    class Widget
    {
    public:
      Widget(Widget&& rhs):name(std::move(rhs.name)){}
    private:
      std::string name;
    };
    

    而万能引用则有所不同,它一般是通过std::forward来进行转换:

    class Widget
    {
    public:
      template<typename T>
      void setName(T&& newName)
      {name = std::forward<T>(newName);}
    private:
      std::string name;
    };
    

    在这种情况下,若newName是一个左值引用,则forward函数不会对它进行操作,它的返回值仍然是一个左值引用;若newName是一个右值,则会进行std::move的转换。用户使用时无需区分,这也正是它“万能”之处。
    举个例子:

    Widget w;
    std::string c = "123";
    w.setName(w);            //传入是一个左值,forward返回左值引用
    w.setName("123");        //传入是一个右值,forward返回右值引用
    w.setName(std::move(w)); //传入是一个右值,forward返回右值引用
    

    引用折叠

    你一定会很奇怪,为什么万能引用的形式明明是T&&,却既可以代表左值又可以代表右值。这就要涉及到C++的引用折叠语法了。
    首先,C++不支持“引用的引用”这种概念,这样的代码在C++中是非法的:

    int x;
    auto& & rx = x; 
    

    但是,假设我们向前面的万能引用函数f传入一个左值引用:

    Widget w = w;
    f(w);  //T的推导型别为Widget&
    

    那么实例化的结果如下:

    void f(Widget& && param);
    

    之所以这样的代码能通过,是因为在特殊的情况下(比如模板实例化),C++应用了引用折叠的语法。
    有左值和右值两种引用,所以就有四种可能的组合:左值-左值、左值-右值、右值-左值、右值-右值,如果引用的引用出现在允许的语境,改双重引用会被折叠成单个引用:

    如果任一引用为左值引用,则结果为左值引用。否则(即两个都为右值引用),结果为右值引用。
    

    因此上述例子中,最终将param推导为左值引用。
    此外,auto的型别推导也会应用引用折叠的场景,例如:

    Widget w;
    auto &&w1 =w;  //w1是个左值
    

    话说到这里,我们就可以更深入地理解万能引用,其实它就是满足了下面两个条件的语境中的右值引用:
    1.型别推导的过程会区别左值和右值。T型别的左值推导结果为T&,而T型别的右值推导结果为T。
    2.会发生引用折叠。

  • 相关阅读:
    js处理数学经典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一 对兔子,假如兔子都不死,问每个月的兔子总数为多少?
    js的水仙花数的输出
    js中关于一个数组中最大、最小值以及它们的下标的输出的一种解决办法
    通过HMI显示西门子SINAMICS V90伺服驱动器报警代码和报警信息
    outlook Archive 归档减小服务器文件夹大小
    西门子触摸屏TP,KTP的报警历史记录数量
    解决HP ZBOOK 蓝屏, 释放静电的方法
    LOGO! 8.2 与 WinCC之间以太网通信的地址对应关系
    Windows10 文件索引功能,替代everything
    行星减速机的精度和强度
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/14994899.html
Copyright © 2020-2023  润新知