关于右值参考的一些澄清

时间:2010-04-05 09:08:02

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

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

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

下一篇:当你不关心移动语义时,是否有任何限制会导致在处理函数参数时,非const引用优先于rvalue引用? void function(T&);超过void function(T&&);从调用者的角度来看,能够传递函数临时值有时是有用的,因此似乎只要有可能就这样做就应该授予该选项。并且rvalue引用本身就是lvalues,因此你不能无意中调用move-constructor而不是copy-constructor,或类似的东西。我没有看到一个缺点,但我确信有一个。

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

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

除了它没有做任何事情之外,该代码有什么问题吗?我的直觉当然不是,因为改变右值参考是他们存在的一个重点。如果传递的值是合法的const,编译器将捕获它并对你大喊大叫。但从各方面来看,这是一种可能出于某种原因而实施的机制,所以我只是想确认我没有做任何愚蠢的事情。

3 个答案:

答案 0 :(得分:10)

  

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

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


  

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

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

  

[注意:rv仍然是一个有效的对象。其状态未指明。 - 结束说明]

我认为这意味着该对象仍然可以参与任何不说明任何前提条件的操作。这包括CopyConstructible,CopyAssignable,Destructible等。请注意,从核心语言的角度来看,这对于您自己的对象不需要任何内容​​。只有在您触摸声明这些要求的标准库组件时才会发生这些要求。


  

下一篇:当你不关心移动语义时,是否有任何限制会导致在处理函数参数时,非const引用优先于rvalue引用?

遗憾的是,这主要取决于参数是否在函数模板中并使用模板参数:

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&用于nonconst左值(并且是更好的匹配),以及{ {1}}对于const lvalues(并且是不明确的)。 据我所知,没有部分排序规则可以消除歧义。然而,这是is already known

U const&修改--

尽管存在问题报告,但我认为这两个模板并不含糊。部分排序会使第一个模板更加专业化,因为在删除了引用修饰符和--之后,我们会发现两个类型都是相同的,然后注意第一个模板对const的引用。标准说(const

  

对于给定类型,如果在两个方向上推导成功(即,上述转换后类型相同),并且参数模板中的类型比参数模板中的类型更具cv-quali ed(如上所述)该类型被认为比另一类更专业。

     

如果考虑的每种类型,给定的模板至少对所有类型都是专用的,对某些类型的集合更专用,而另一种模板对于任何类型都不是更专用的,或者至少不适用于任何类型,那么给定的模板比其他模板更专业。

这使14.9.2.4模板成为部分排序的胜利者(GCC确实是正确的选择)。

T const&修改结束--


  

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

this article中很好地解释了这一点。使用--的第二次调用仅使用非对齐rvalues。程序的其余部分将不会注意到它们是否被修改,因为它们之后将无法再访问这些rvalues!并且您传递的function2不是类类型,因此创建隐藏的临时值,然后传递给5右值引用。调用int&&的代码将无法在此处访问该隐藏对象,因此它不会发现任何更改。

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

function2

您已明确请求移动SomeComplexObject o; function2(move(o)); ,因此将根据其移动规范对其进行修改。但是,移动是逻辑上非修改操作(参见文章)。这意味着您是否应该从调用代码中观察到是否移动:

o

如果删除移动的行,行为仍然是相同的,因为它仍然会被覆盖。但是,这意味着在移动SomeComplexObject o; moveit(o); // #1 o = foo; 之后使用o的值的代码是错误,因为它打破了moveit与调用代码之间的隐式契约。因此,标准没有规定从容器移动的具体值。

答案 1 :(得分:4)

  

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

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

  

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

它当然取决于你如何实现move-constructor和move-assignment运算符。但是,如果要在标准容器中使用对象,则必须遵循MoveConstructibleMoveAssignable概念,该概念表示对象仍然有效,但保留在未指定状态,即你绝对可以摧毁它。

答案 2 :(得分:2)

包含在utility


Here是我读到的关于rvalues的文章。

抱歉,我无法帮助你休息。