关于右值引用的一些澄清
第一: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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
请参阅
20.3
实用程序组件,
。显然,该物体应该仍然是可破坏的。但除此之外,我认为仍然可分配是一个好主意。标准规定,对于满足“MoveConstructible”和“MoveAssignable”的对象:
我认为,这意味着该对象仍然可以参与任何不声明任何先决条件的操作。这包括 CopyConstructible、CopyAssignable、Destructible 等。请注意,从核心语言的角度来看,这不需要您自己的对象做任何事情。仅当您接触陈述这些要求的标准库组件时,这些要求才会发生。
不幸的是,这主要取决于参数是否在函数模板中并使用模板参数:
但是对于函数模板
你不能这么说,因为第二个模板在使用左值调用后将具有以下参数:合成声明类型
U&
表示非 const 左值(并且是更好的匹配),而U const&
表示 const 左值(并且是不明确的)。据我所知,没有偏序规则来消除第二个歧义。但是,这个已知。--
编辑--
尽管存在该问题报告,但我认为这两个模板并不含糊。偏序会让第一个模板更加特殊,因为去掉引用修饰符和const之后,我们会发现这两个类型是相同的,然后注意到第一个模板有一个对const的引用。标准说(
14.9.2.4
)这使得
T const&
模板成为部分排序的获胜者(GCC 选择它确实是正确的)。--
编辑结束--
这篇文章对此进行了很好的解释。使用
function2
的第二次调用仅采用非常量右值。程序的其余部分不会注意到它们是否被修改,因为之后它们将无法再访问这些右值!并且您传递的5
不是类类型,因此会创建一个隐藏的临时变量,然后传递给int&&
右值引用。调用function2
的代码将无法访问此处的隐藏对象,因此它不会注意到任何更改。另一种情况是,如果您执行此操作:
您已明确请求移动
o
,因此它将根据其移动规范进行修改。然而,移动是一个逻辑上不可修改的操作(请参阅文章)。这意味着您不应该从调用代码中观察到您是否移动:如果您删除移动的行,行为仍然会是相同的,因为它无论如何都会被覆盖。然而,这意味着在移动后使用
o
的值的代码是坏,因为它破坏了moveit
和调用代码。因此,该标准没有对移出集装箱的具体价值做出规定。See
20.3
Utility components,<utility>
.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":
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.
This, unfortunately, crucially depends on whether the parameter is in a function template and uses a template parameter:
However for a function template
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), andU 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
)This makes the
T const&
template the winner of partial ordering (and GCC is indeed correct to choose it).--
Edit End--
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 the5
you pass is not a class type, so a hidden temporary is created and then passed to theint&&
rvalue reference. The code callingfunction2
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:
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: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 betweenmoveit
and the calling code. Thus, the Standard makes no specification about the concrete value of a moved from container.std::move
和std::forward
在
中声明。请参阅第 20.3 节[实用程序]开头的概要。当然,这取决于您如何实现移动构造函数和移动赋值运算符。但是,如果您想在标准容器中使用对象,则必须遵循
MoveConstructible
和MoveAssignable
概念,这表示该对象仍然有效,但保留在 < em>未指定状态,即你绝对可以摧毁它。std::move
andstd::forward
are declared in<utility>
. See the synopsis at the beginning of section 20.3[utility].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
andMoveAssignable
concepts, which says that the object remains valid, but is left in unspecified state, i.e. you definitely can destroy it.由 实用程序 包含
此处 是我读过的关于右值的文章。
我无法帮助你休息,抱歉。
included by utility
Here is the article I read about rvalues.
I can't help you with rest, sorry.