关于右值引用的一些澄清

发布于 2024-08-28 09:35:09 字数 940 浏览 7 评论 0原文

第一:std::move 和 std::forward 定义在哪里?我知道它们的作用,但我找不到任何标准标头需要包含它们的证据。在 gcc44 中,有时 std::move 可用,有时则不可用,因此明确的 include 指令会很有用。

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

接下来:当您不关心移动语义时,在处理函数参数时是否存在任何限制会导致非常量引用优于右值引用? 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,编译器会捕获它并对你大喊大叫。但从表面上看,这是一种机制的变通,大概是有原因的,所以我想确认一下我没有做任何愚蠢的事情。

First: where are std::move and std::forward defined? I know what they do, but I can't find proof that any standard header is required to include them. In gcc44 sometimes std::move is available, and sometimes its not, so a definitive include directive would be useful.

When implementing move semantics, the source is presumably left in an undefined state. Should this state necessarily be a valid state for the object? Obviously, you need to be able to call the object's destructor, and be able to assign to it by whatever means the class exposes. But should other operations be valid? I suppose what I'm asking is, if your class guarantees certain invariants, should you strive to enforce those invariants when the user has said they don't care about them anymore?

Next: when you don't care about move semantics, are there any limitations that would cause a non-const reference to be preferred over an rvalue reference when dealing with function parameters? void function(T&); over void function(T&&); From a caller's perspective, being able to pass functions temporary values is occasionally useful, so it seems as though one should grant that option whenever it is feasible to do so. And rvalue references are themselves lvalues, so you can't inadvertently call a move-constructor instead of a copy-constructor, or something like that. I don't see a downside, but I'm sure there is one.

Which brings me to my final question. You still can not bind temporaries to non-const references. But you can bind them to non-const rvalue references. And you can then pass along that reference as a non-const reference in another function.

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

Besides the fact that it doesn't do anything, is there anything wrong with that code? My gut says of course not, since changing rvalue references is kind of the whole point to their existence. And if the passed value is legitimately const, the compiler will catch it and yell at you. But by all appearances, this is a runaround of a mechanism that was presumably put in place for a reason, so I'd just like confirmation that I'm not doing anything foolish.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

_畞蕅 2024-09-04 09:35:09

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

请参阅 20.3 实用程序组件,


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

显然,该物体应该仍然是可破坏的。但除此之外,我认为仍然可分配是一个好主意。标准规定,对于满足“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& 表示非 const 左值(并且是更好的匹配),而 U const& 表示 const 左值(并且是不明确的)。 据我所知,没有偏序规则来消除第二个歧义。但是,这个已知

-- 编辑 --

尽管存在该问题报告,但我认为这两个模板并不含糊。偏序会让第一个模板更加特殊,因为去掉引用修饰符和const之后,我们会发现这两个类型是相同的,然后注意到第一个模板有一个对const的引用。标准说(14.9.2.4

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

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

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

-- 编辑结束 --


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

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

另一种情况是,如果您执行此操作:

SomeComplexObject o;
function2(move(o));

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

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

如果您删除移动的行,行为仍然会是相同的,因为它无论如何都会被覆盖。然而,这意味着在移动后使用 o 的值的代码是,因为它破坏了 moveit 和调用代码。因此,该标准没有对移出集装箱的具体价值做出规定。

First: where are std::move and std::forward defined?

See 20.3 Utility components, <utility>.


When implementing move semantics, the source is presumably left in an undefined state. Should this state necessarily be a valid state for the object?

Obviously, the object should still be destructibly. But further than that, I think it's a good idea to be still assignable. The Standard says for objects that satisfy "MoveConstructible" and "MoveAssignable":

[ Note: rv remains a valid object. Its state is unspecified. — end note ]

This would mean, I think, that the object can still participate in any operation that doesn't state any precondition. This includes CopyConstructible, CopyAssignable, Destructible and other things. Notice that this won't require anything for your own objects from a core language perspective. The requirements only take place once you touch Standard library components that state these requirements.


Next: when you don't care about move semantics, are there any limitations that would cause a non-const reference to be preferred over an rvalue reference when dealing with function parameters?

This, unfortunately, crucially depends on whether the parameter is in a function template and uses a template parameter:

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

However for a function template

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

You can't say that, because the second template will, after being called with an lvalue, have as parameter of the synthesized declaration the type U& for nonconst lvalues (and be a better match), and U const& for const lvalues (and be ambiguous). To my knowledge, there is no partial ordering rule to disambiguate that second ambiguity. However, this is already known.

-- Edit --

Despite that issue report, I don't think that the two templates are ambiguous. Partial ordering will make the first template more specialized, because after taking away the reference modifiers and the const, we will find that both types are the same, and then notice that the first template had a reference to const. The Standard says (14.9.2.4)

If, for a given type, deduction succeeds in both directions (i.e., the types are identical after the transfor-mations above) and if the type from the argument template is more cv-qualified than the type from the parameter template (as described above) that type is considered to be more specialized than the other.

If for each type being considered a given template is at least as specialized for all types and more specialized for some set of types and the other template is not more specialized for any types or is not at least as specialized for any types, then the given template is more specialized than the other template.

This makes the T const& template the winner of partial ordering (and GCC is indeed correct to choose it).

-- Edit End --


Which brings me to my final question. You still can not bind temporaries to non-const references. But you can bind them to non-const rvalue references.

This is nicely explained in this article. The second call using function2 only takes nonconst rvalues. The rest of the program won't notice if they are modified, because they won't be able to access those rvalues afterwards anymore! And the 5 you pass is not a class type, so a hidden temporary is created and then passed to the int&& rvalue reference. The code calling function2 won't be able to access that hidden object here, so it won't notice any change.

A different situation is if you do this one:

SomeComplexObject o;
function2(move(o));

You have explicitly requested that o is moved, so it will be modified according to its move specification. However moving is a logically non-modifying operation (see the article). This means whether you move or not shouldn't be observable from the calling code:

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

If you erase the line that moves, behavior will still be the same, because it's overwritten anyway. This however means that code that uses the value of o after it has been moved from is bad, because it breaks this implicit contract between moveit and the calling code. Thus, the Standard makes no specification about the concrete value of a moved from container.

我是男神闪亮亮 2024-09-04 09:35:09

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

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

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

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

where are std::move and std::forward defined?

std::move and std::forward are declared in <utility>. See the synopsis at the beginning of section 20.3[utility].

When implementing move semantics, the source is presumably left in an undefined state.

It of course depends on how you implement the move-constructor and move-assignment operator. If you want to use your objects in standard containers, however, you have to follow the MoveConstructible and MoveAssignable concepts, which says that the object remains valid, but is left in unspecified state, i.e. you definitely can destroy it.

痴者 2024-09-04 09:35:09

实用程序 包含


此处 是我读过的关于右值的文章。

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

included by utility


Here is the article I read about rvalues.

I can't help you with rest, sorry.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文