【问题标题】:Some clarification on rvalue references关于右值引用的一些说明
【发布时间】:2011-02-04 09:04:45
【问题描述】:

首先:std::movestd::forward 在哪里定义?我知道他们做了什么,但我找不到任何标准标题都需要包含它们的证据。在 gcc44 中,有时std::move 可用,有时不可用,因此明确的包含指令会很有用。

在实现移动语义时,源可能处于未定义状态。该状态是否必须是对象的有效状态?显然,您需要能够调用对象的析构函数,并且能够通过类公开的任何方式分配给它。但是其他操作应该有效吗?我想我要问的是,如果你的类保证某些不变量,当用户说他们不再关心它们时,你是否应该努力强制执行这些不变量?

下一步:当您不关心移动语义时,在处理函数参数时,是否有任何限制会导致非常量引用优于右值引用? void function(T&); 优于 void function(T&&); 从调用者的角度来看,能够传递函数临时值有时很有用,因此似乎只要可行,就应该授予该选项。并且右值引用本身就是左值,因此您不能无意中调用移动构造函数而不是复制构造函数或类似的东西。我看不出有什么缺点,但我敢肯定有一个缺点。

这让我想到了最后一个问题。您仍然不能将临时对象绑定到非常量引用。但是您可以将它们绑定到非常量右值引用。然后,您可以将该引用作为非常量引用传递给另一个函数。

void function1(int& r) { r++; }
void function2(int&& r) { function1(r); }
int main() { 
    function1(5); //bad
    function2(5); //good
}

除了它什么都不做之外,那段代码有什么问题吗?我的直觉说当然不是,因为更改右值引用是它们存在的全部意义。如果传递的值是合法的 const,编译器会捕捉到它并对你大喊大叫。但从表面上看,这是一种可能出于某种原因而实施的机制的变通,所以我只想确认我没有做任何愚蠢的事情。

【问题讨论】:

    标签: c++ c++11 rvalue-reference


    【解决方案1】:

    首先:std::move 和 std::forward 定义在哪里?

    请参阅20.3 实用程序组件,<utility>


    在实现移动语义时,源可能处于未定义状态。该状态是否必须是对象的有效状态?

    显然,该对象仍应是可破坏的。但除此之外,我认为仍然可以分配是个好主意。标准规定满足“MoveConstructible”和“MoveAssignable”的对象:

    [ 注意:rv 仍然是一个有效的对象。其状态未指定。 ——尾注]

    我认为,这意味着该对象仍然可以参与任何未声明任何先决条件的操作。这包括 CopyConstructible、CopyAssignable、Destructible 等。请注意,从核心语言的角度来看,这对您自己的对象不需要任何东西。只有在您接触到说明这些要求的标准库组件时,才会出现这些要求。


    下一步:当您不关心移动语义时,在处理函数参数时,是否有任何限制会导致非常量引用优于右值引用?

    不幸的是,这关键取决于参数是否在函数模板中并使用模板参数:

    void f(int const&); // takes all lvalues and const rvalues
    void f(int&&); // can only accept nonconst rvalues
    

    但是对于函数模板

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

    你不能这么说,因为第二个模板在被一个左值调用后,作为综合声明的参数,类型为 U&amp; 用于非常量左值(并且是一个更好的匹配)和U const&amp;对于 const 左值(并且是模棱两可的)。 据我所知,没有偏序规则可以消除第二个歧义。但是,is already known

    --编辑--

    尽管有问题报告,但我不认为这两个模板有歧义。偏序会使第一个模板更加特化,因为去掉引用修饰符和const后,我们会发现两个类型是一样的,然后注意到第一个模板引用了const。标准说 (14.9.2.4)

    如果对于给定类型,推导在两个方向上都成功(即,在上述转换后类型相同)并且如果来自参数模板的类型比来自参数模板的类型更具有 cv 限定(如上所述)该类型被认为比其他类型更专业。

    如果对于每个被考虑的类型,一个给定的模板至少对所有类型都专门化,并且对某些类型集更专门化,而另一个模板对任何类型都没有更专门化,或者至少对任何类型都没有专门化,那么给定的模板比另一个模板更专业。

    这使得T const&amp; 模板成为部分排序的赢家(GCC 确实选择它是正确的)。

    --编辑结束--


    这让我想到了最后一个问题。您仍然不能将临时对象绑定到非常量引用。但是您可以将它们绑定到非常量右值引用。

    这在this article 中有很好的解释。使用function2 的第二次调用仅采用非常量右值。程序的其余部分不会注意到它们是否被修改,因为之后它们将无法访问这些右值!而且您传递的5 不是类类型,因此会创建一个隐藏的临时对象,然后将其传递给int&amp;&amp; 右值引用。调用function2 的代码将无法访问此处的隐藏对象,因此它不会注意到任何变化。

    如果你这样做,另一种情况是:

    SomeComplexObject o;
    function2(move(o));
    

    您已明确要求移动o,因此将根据其移动规范对其进行修改。然而,移动是一个逻辑上不可修改的操作(参见文章)。这意味着不应该从调用代码中观察到您是否移动:

    SomeComplexObject o;
    moveit(o); // #1
    o = foo;
    

    如果删除移动的行,行为仍然相同,因为无论如何它都会被覆盖。然而,这意味着在移动后使用 o 值的代码是错误,因为它破坏了 moveit 和调用代码之间的隐式契约。因此,该标准没有说明从容器中移动的具体值。

    【讨论】:

    • 感谢您的精心回复。我想我对事情的理解比以前少了,但这不是你自己的错。根据那篇文章,void f(int&amp;&amp;); 之类的函数不能接受左值,除非它们被显式转换?这绝对不是我所看到的行为,所以我认为这是 gcc-4.4 实现中的一个缺点。这使得我一直在做的所有测试以了解一切如何交互几乎毫无价值:-/
    • @Dennis,是的,前段时间在工作草案中进行了更改:int&amp;&amp; 不再隐式绑定到左值。太危险了。
    • “这篇文章”链接已失效。你能把它换成现在的吗?
    • @JohannesSchaub-litb “这篇文章”链接已失效。你能把它换成现在的吗?
    【解决方案2】:

    std::move 和 std::forward 定义在哪里?

    std::movestd::forward&lt;utility&gt; 中声明。请参阅第 20.3 节[实用程序]开头的概要。

    在实现移动语义时,源可能处于未定义状态。

    这当然取决于您如何实现移动构造函数和移动赋值运算符。但是,如果您想在标准容器中使用您的对象,则必须遵循 MoveConstructibleMoveAssignable 概念,这表示对象仍然有效,但仍处于 未指定 状态,即你绝对可以摧毁它。

    【讨论】:

      【解决方案3】:

      包含在utility


      Here 是我读到的关于右值的文章。

      我不能帮你休息,对不起。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-06-22
        • 2011-06-17
        • 2019-05-14
        • 1970-01-01
        • 1970-01-01
        • 2019-06-11
        • 2020-04-28
        • 2021-09-28
        相关资源
        最近更新 更多