zoukankan      html  css  js  c++  java
  • Item 24: 区分右值引用和universal引用

    本文翻译自《effective modern C++》,由于水平有限。故无法保证翻译全然正确,欢迎指出错误。

    谢谢!

    博客已经迁移到这里啦

    古人曾说事情的真相会让你认为非常自在,可是在适当的情况下,一个良好的谎言相同能解放你。

    这个Item就是这样一个谎言。可是,由于我们在和软件打交道,所以让我们避开“谎言”这个词。换句话来说:本Item是由“抽象”组成的。

    为了声明一个指向T类型的右值引用。你会写T&&。

    因此我们能够“合理”地假设:假设你在源码中看到“T&&”。你就看到了一个右值引用。可惜地是,它没有这么简单:

    void f(Widget&& param);         // 右值引用
    
    Widget&& var1 = Widget();       // 右值引用
    
    auto&& var2 = var1;             // 不是右值引用
    
    template<typename T>
    void f(std::vector<T>&& param); // 右值引用
    
    template<typename T>
    void f(T&& param);              // 不是右值引用
    

    其实,“T&&”有两个不同的意思。当然,当中一个是右值引用。这样引用行为就是你所期望的:它们仅仅绑定到右值上去,而且它们的主要职责就是去明白一个对象是能够被move的。

    “T&&”的另外一个意思不是左值引用也不是右值引用。这种引用看起来像是在源文件里的右值引用(也就是,“T&&”),可是它能表现得像是一个左值引用(也就是“T&”)一样。它这种两重意义让它能绑定到左值(就像左值引用)上去。也能绑定到右值(就像右值引用)上去。另外,它能绑定到const或非const对象上去,也能绑定到volatile或非volatile对象上去,甚至能绑定到const加volatile的对象上去。它能绑定到差点儿不论什么东西上去。这样空前灵活的引用理应拥有它们自己的名字。我叫它们universal引用(万能引用)。

    universal引用出如今两种上下文中。最通用的情况是在函数模板參数中,就像来自于上面演示样例代码的这个样例一样:

    template<typename T>
    void f(T&& param);              // param是一个universal引用
    

    第二个情况是auto声明,包含上面演示样例代码中的这一行代码:

    auto&& var2 = var1;             // var2是一个universal引用
    

    这两个情况的共同点就是它们都存在类型推导。

    在模板f中。param的类型正在被推导,而且在var2的声明式中,var2的类型正在被推导。把它们和以下的样例(它们不存在类型推导,相同来自上面的演示样例代码)比較一下,能够发现。假设你看到不存在类型推导的“T&&”时,你能把它视为右值引用:

    void f(Widget&& param);         // 没有类型推导
                                    // param是右值引用
    
    Widget&& var1 = Widget();       // 没有类型推导
                                    // param是右值引用
    

    由于universal引用是引用,它们必须被初始化。universal引用的初始化决定了它代表一个右值还是一个左值。假设初始化为一个右值,universal引用相应右值引用。

    假设初始化为一个左值,universal引用相应一个左值引用。对于那些属于函数參数的universal引用。它在调用的地方被初始化:

    template<typename T>
    void f(T&& param);              // param是一个universal引用
    
    Widget w;
    f(w);                           // 左值被传给f。param的类型是
                                    // Widget&(也就是一个左值引用)
    
    f(std::move(w));                // 右值被传给f,param的类型是
                                    // Widget&&(也就是一个右值引用)
    

    要让一个引用成为universal引用,类型推导是其必要不补充条件。引用声明的格式必须同一时候正确才行,而且格式非常严格。它必须正好是“T&&”。

    再看一次这个我们之前在演示样例代码中看过的样例:

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

    当f被调用时。类型T将被推导(除非调用者显式地指定它,这种边缘情况我们不关心)。可是param类型推导的格式不是“T&&”,而是“std::vector&&”。

    依照上面的规则,排除了param成为一个universal引用的可能性。因此param是一个右值引用。有时候你的编译器会非常高兴地为你确认你是否传入了一个左值给f:

    std::vector<int> v;
    f(v);                           // 错误!不能绑定一个左值到右值
                                    // 引用上去
    

    甚至一个简单的const属性的出场就足以取消引用成为universal的资格:

    template<typename T>
    void f(const T&& param);        // param是一个右值引用
    

    假设你在一个模板中,而且你看到一个“T&&”类型的函数參数,你可能认为你能假设它是一个universal引用。可是你不能,由于在模板中不能保证类型推导的存在。

    考虑一下std::vector中的这个push_back成员函数:

    template<class T, class Allocator = allocator<T>>       //来自c++标准库
    class vector {
    public:
        void push_back(T&& x);
        ...
    };
    

    push_back的參数全然符合universal引用的格式,可是在这个情况中没有类型推导发生。由于push_back不能存在于vector的特定实例之外,而且实例的类型就全然能决定push_back的声明类型了。也就是说

        std::vector<Widget> v;
    

    使得std::vector模板被实例化为以下这样:

    class vector<Widget, allocator<Widget>> {
    public:
        void push_back(Widget&& x);     //右值引用
        ...
    };
    

    如今你能清楚地发现push_back没实用到类型推导。

    vector的这个push_back(vector中有两个push_back函数)总是声明一个类型是rvalue-reference-to-T(指向T的右值引用)的參数。

    不同的是。std::vector中和push_back概念上类似的emplace_back成员函数用到了类型推导:

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

    在这里,类型參数Args独立于vector的类型參数T,所以每次emplace_back被调用的时候。Args必须被推导。

    (好吧。Args其实是一个參数包。不是一个类型參数,可是为了讨论的目的,我们能把它视为一个类型參数。)

    其实emplace_back的类型參数被命名为Args(不是T)。可是它仍然是一个universal引用。之前我说universal引用的格式必须是“T&&”。在这里重申一下,我没要求你必须使用名字T。

    举个样例。

    以下的模板使用一个universal引用,由于格式(“type&&”)是正确的。而且param的类型将被推导(再说一次,除了调用者显式指定类型的边缘情况):

        template<typename MyTemplateType>       // param是一个
        void someFunc(MyTemplateType&& param);  // universal引用
    

    我之前说过auto变量也能是universal引用。

    更加精确一些。用auto&&的格式被推导的变量是universal引用。由于类型推导有发生,而且它有正确的格式(“T&&”)。auto universal引用不像用于函数模板參数的universal引用那么常见,可是他们有时候会在C++11中突然出现。

    他们在C++14中出现的频率更高。由于C++14的lambda表达式能够声明auto&&參数。举个样例,假设你想要写一个C++14的lambda来记录随意函数调用花费的时间,你能这么做:

    auto timeFuncInvocation =
        [](auto&& func, auto&&... param)
        {
            start timer;
            std::forward<decltype(func)>(func){             // 用params
                std::forward<decltype(params)>(params)...   // 调用func
            };
            停止timer并记录逝去的时间。

    };

    假设你对lambda中“std::forward

                你要记住的事
    • 假设一个函数模板參数有T&&的格式,而且会被推导,或者一个对象使用auto&&来声明,那么參数或对象就是一个universal引用。

    • 假设类型推导的格式不是准确的type&&,或者假设类型推导没有发生。type&&就是一个右值引用。
    • 假设用右值来初始化,universal引用相当于右值引用。假设用左值来初始化。则相当于左值引用。
  • 相关阅读:
    一个简易邮件群发软件设计与实现
    一种公众号回复关键词机制
    Oracle 异常 ORA-01861: literal does not match format string(字符串格式不匹配)
    Linux使用命令
    IDEA在引入Maven项目后Dependencies中在出现红色波浪线
    MySQL安装Write configuration file 提示:configuration file template my.ini Error code-1
    redis批量删除键的操作
    在WINDOWS服务器下设置MARIADB自动备份的方法
    xampp3.2下mysql中文乱码终极解决方案
    CentOS 7虚拟机下模拟实现nginx负载均衡
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7160285.html
Copyright © 2011-2022 走看看