RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1189656
Accepted
megorit
megorit
Asked:2021-10-13 16:29:17 +0000 UTC2021-10-13 16:29:17 +0000 UTC 2021-10-13 16:29:17 +0000 UTC

std::move 和 std::forward 到底是做什么的?

  • 772

我熟悉移动语义和完美转发。但我有一个问题,如下:

void function(Type&& argument)
{
    std::is_lvalue_reference<decltype(argument)>::value;
    std::is_rvalue_reference<decltype(argument)>::value;
    std::move(argument);
}

template <typename Type>
void function(Type&& argument)
{
    std::is_lvalue_reference<decltype(argument)>::value;
    std::is_rvalue_reference<decltype(argument)>::value;
    std::forward<Type>(argument);
}

如果使用std::is_lvalue_referenceand std::is_rvalue_reference,那么很明显 C++ 知道函数内部的内容(左值或右值)。所以它std::forward做了以下事情:如果一个右值来了,它返回一个右值,如果一个左值来了,它返回一个左值。那些。有一些看似毫无意义的工作正在进行。但是,没有std::forward“完美转移”是行不通的。没有std::move“重定位语义”是行不通的。它是通过实验检查的。那么到底有什么意义呢?

c++
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Croessmah stands with Russia
    2021-10-14T02:06:10Z2021-10-14T02:06:10Z

    考虑一个简单的例子

    template<typename T> 
    T&& func(T&& t)
    {
        return t;
    }
     
    int main() 
    { 
        double v1 = 0.9;
        const double v2 = 0.9;
        func(v1);//Ok
        func(v2);//Ok
        func(42);//Ошибка
    }
    

    为了理解这里发生了什么,让我们分析两个在类型推断上下文中起作用的规则。

    规则一。 推断类型时(在模板中或用于auto),类型的引用性被丢弃,即,如果类型是从参数推断的,则类型将不是引用类型,除非使用右值一起推断类型参考,但稍后会详细介绍。如果需要引用,那么使用这样的模板,使用时需要指定类型:

    template<class T> 
    void func(T val) 
    {}
    //...
    double x{};
    func(x);//T = double
    func(std::move(x));//T = double
    func(33.6);//T = double
    func<double&>(x);//T = double&
    func<double&&>(std::move(x));//T = double&&
    func<double&&>(33.8);//T = double&&
    

    规则二。 在类型推断期间可以折叠(折叠)链接。这意味着如果类型推断产生“对引用的引用”,那么它就会崩溃。我将折叠规则作为真值盘:

        &  &&
    &   &  &
    &&  &  &&
    

    那些。如果我们获得“对引用的引用”,那么结果类型将始终是左值引用,除非获得对右值引用的右值引用,在这种情况下,最终类型将是右值引用。

    为了更清楚,让我们看一下带有 typedef 的示例:

    typedef double& LVR;  //double&
    typedef LVR& LLVR;    //double& + & = double&
    typedef LVR&& RLVR;   //double& + && = double&
    typedef double&& RVR; //double&&
    typedef RVR& LRVR;    //double&& + & = double&
    typedef RVR&& RRVR;   //double&& + && = double&&
    

    这两种类型推理规则允许您组织可以引用任何内容的参数。

    让我们再看第一个例子:

    template<class T> T&& func(T&& t)
    {
        return t;
    };
     
    int main() {
     
        double v1 = 0.9;
        const double v2 = 0.9;
        func(v1);//1
        func(v2);//2
        func(42);//3
    };
    

    情况一:func(v1)有 v1类型double。func在参数中接受类型参数T&&(T从参数推断)。表达式v1将具有类别lvalue,因此v1不能绑定到double&&。但是,如果输出T不是 as double,而是 as double&,那么我们得到的情况double& &&是 ,这意味着链接将被折叠到double&。事实证明,T必须将类型完全推导出为lvalue-reference - double&才能成功。在返回值中,我们有T&&,这意味着推导时,我们T如何double&得到double& &&,这也折叠为double&。我们得到一个函数:

    double& func(double& r)
    {
       return r;
    }
    

    很明显,在这个函数中一切都很好,没有错误。

    在第二种情况下,fucn(v2), v2是 type const double,所以一切都与 with 相同v1,除了类型将具有限定符const:

    const double& fun(const double& r)
    {
       return r;
    }
    

    而我们代码中的第三种情况更有趣—— func(67). 67- 将给出一个临时对象类型int,即 我们最终得到一个带有右值类别的表达式。右值可以绑定到非const 右值引用,这意味着它将T输出为double。类型推断成功T可以取 as double,然后得到代码:

    double&& fun(double&& r)
    {
       return r;//lvalue с типом double&& - ошибка
    }
    

    那么为什么会发生错误呢?但是因为变量本身r是一个右值引用,但它是一个命名变量,它的名字(r)的表达式将具有左值类别。如您所知,lvalue不会隐式转换为rvalue-reference ,这就是尝试返回引用r时发生错误的原因。现在让我们添加std::forward代码:

    template<class T> T&& func(T&& t)
    {
        return std::forward<T>(t);//Добавили std::forward - всё хорошо. 
    };
    

    为什么错误消失了?让我们考虑一个简化的设备std::forward并编写我们自己的简单模拟。

    //std::forward
    template <class T> constexpr T&& forward(typename remove_reference<T>::type& t) noexcept;
    template <class T> constexpr T&& forward(typename remove_reference<T>::type&& t) noexcept;
    //Returns: static_cast<T&&>(t).
    

    我们实现了自己的forward,但为了简化我们丢弃constexpr,等等:

    template <class T>
    T&& forward(std::remove_reference_t<T>& t) noexcept
    {
       return static_cast<T&&>(t);
    }
     
    template <class T>
    T&& forward(std::remove_reference_t<T>&& t) noexcept
    {
       return static_cast<T&&>(t);
    }
    

    将此与我们的函数进行比较:

    template<class T> T&& func(T&& t)
    {
        return forward<T>(t);//Тоже всё работает и ошибок нет
    }
    

    那么有什么魔力呢?func假设我们用v1-调用lvalue,那么我们得到它T的输出为double&,这意味着它t是一个左值引用。现在让我们看看工作forward<T&>。我们用类型实例化它T- double&。所以我们得到以下中间代码:

    double& && forward(std::remove_reference_t<double&>& t) noexcept
    {
       return static_cast<double& &&>(t);
    }
    

    remove_reference_t将删除链接,并在链接static_cast的返回值中和中折叠并获取函数:

    double& forward(double& t) noexcept
    {
       return static_cast<double&>(t);
    }
    

    最终forward给出double&。因此,传入func lvalue,我们得到:

    double& func(double& t)
    {
        return forward<double&>(t);//lvalue с типом double&
    }
    

    让我们func用一个具有类别rvalue( xvalue) 的参数(表达式)来调用它——例如,std::move(v1)。我们得到Tin的类型func将显示为double,这意味着我们forward使用类型double( forward<double>(t)) 进行实例化,同时将其作为参数传递给函数rvalue-reference,我们将得到以下中间代码:

    double&& forward(std::remove_reference_t<double>& t) noexcept
    {
       return static_cast<double&&>(t);
    }
    

    最后,整个函数func将没有错误:

    double&& func(double&& t)
    {
        return forward<double>(t); //xvalue с типом double&&
    }
    

    此代码forward将已经返回double&&(表达式类别 - xvalue ),而不是double&(表达式类别 - lvalue )。

    也就是说,forward根据模板参数和模板函数参数,它要么产生一个右值引用,整个表达式将具有xvalue category ,要么产生一个左值引用,整个表达式将具有lvalue category 。这是所有魔术结束的地方。几个简单的规则,药水就准备好了。

    至于std::move- 这只是static_castrvalue -reference。它总是返回一个右值引用,并且整个表达式都有xvalue类别。

    • 3

相关问题

  • 编译器和模板处理

  • 指针。找到最小数量

  • C++,关于枚举类对象初始化的问题

  • 函数中的二维数组

  • 无法使用默认构造函数创建类对象

  • C++ 和循环依赖

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5