9

第一:在哪里std::move定义std::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,编译器会捕捉到它并对你大喊大叫。但从表面上看,这是一种机制的变通,可能是出于某种原因而实施的,所以我只想确认我没有做任何愚蠢的事情。

4

3 回答 3

9

第一: 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&(并且是更好的匹配)和U const&const 左值(并且是模棱两可)的类型。据我所知,没有偏序规则可以消除第二个歧义。然而,这是众所周知的。

--编辑--

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

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

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

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

--编辑结束--


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

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

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

SomeComplexObject o;
function2(move(o));

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

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

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

于 2010-04-05T09:52:32.683 回答
4

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

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

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

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

于 2010-04-05T09:43:14.587 回答
2

包含在实用程序中


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

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

于 2010-04-05T09:42:27.667 回答