C++11 右值和带有 return 语句的移动语义
我试图理解 C++11 的右值引用和移动语义。
这些示例之间有什么区别,哪些示例不进行矢量复制?
第一个例子:
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
第二个例子:
std::vector<int>&& return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
第三个例子:
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
I'm trying to understand rvalue references and move semantics of C++11.
What is the difference between these examples, and which of them is going to do no vector copy?
First example:
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
Second example:
std::vector<int>&& return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
Third example:
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
这些都不会进行任何额外的复制。即使不使用 RVO,新标准也表明在进行回报时,移动构建优先于复制,我相信。
我确实相信您的第二个示例会导致未定义的行为,因为您正在返回对局部变量的引用。
None of those will do any extra copying. Even if RVO isn't used, the new standard says that move construction is preferred to copy when doing returns I believe.
I do believe that your second example causes undefined behavior though because you're returning a reference to a local variable.
正如第一个答案的评论中已经提到的,
return std::move(...);
构造可以在除返回局部变量之外的情况下产生影响。这是一个可运行的示例,记录了当您返回带有和不带有std::move()
的成员对象时会发生什么:大概,仅
return std::move(some_member);
如果您确实想要移动特定的类成员,例如在class C
表示短期适配器对象且唯一目的是创建struct A
实例的情况下,这是有意义的。请注意,即使
class B
对象是 R-,struct A
总是如何从class B
中复制价值。这是因为编译器无法告知class B
的struct A
实例将不再被使用。在class C
中,编译器确实从std::move()
获取了此信息,这就是struct A
被移动的原因/em>,除非class C
的实例是常量。As already mentioned in comments to the first answer, the
return std::move(...);
construct can make a difference in cases other than returning of local variables. Here's a runnable example that documents what happens when you return a member object with and withoutstd::move()
:Presumably,
return std::move(some_member);
only makes sense if you actually want to move the particular class member, e.g. in a case whereclass C
represents short-lived adapter objects with the sole purpose of creating instances ofstruct A
.Notice how
struct A
always gets copied out ofclass B
, even when theclass B
object is an R-value. This is because the compiler has no way to tell thatclass B
's instance ofstruct A
won't be used any more. Inclass C
, the compiler does have this information fromstd::move()
, which is whystruct A
gets moved, unless the instance ofclass C
is constant.第一个示例
第一个示例返回一个由 rval_ref 捕获的临时值。该临时变量的生命周期将超出
rval_ref
定义,您可以像通过值捕获它一样使用它。这与以下内容非常相似:除了在我的重写中,您显然不能以非常量方式使用 rval_ref 。
第二个示例
在第二个示例中,您创建了一个运行时错误。
rval_ref
现在保存对函数内已解构的tmp
的引用。运气好的话,这段代码会立即崩溃。第三个示例
您的第三个示例大致相当于您的第一个示例。
tmp
上的std::move
是不必要的,实际上可能会导致性能下降,因为它会抑制返回值优化。编码您正在做的事情的最佳方法是:
最佳实践
,即就像在 C++03 中一样。
tmp
在 return 语句中被隐式地视为右值。它将通过返回值优化返回(无复制,无移动),或者如果编译器决定它无法执行 RVO,则它 将使用向量的移动构造函数来执行返回。仅当不执行 RVO,并且返回的类型没有移动构造函数时,才会使用复制构造函数进行返回。First example
The first example returns a temporary which is caught by
rval_ref
. That temporary will have its life extended beyond therval_ref
definition and you can use it as if you had caught it by value. This is very similar to the following:except that in my rewrite you obviously can't use
rval_ref
in a non-const manner.Second example
In the second example you have created a run time error.
rval_ref
now holds a reference to the destructedtmp
inside the function. With any luck, this code would immediately crash.Third example
Your third example is roughly equivalent to your first. The
std::move
ontmp
is unnecessary and can actually be a performance pessimization as it will inhibit return value optimization.The best way to code what you're doing is:
Best practice
I.e. just as you would in C++03.
tmp
is implicitly treated as an rvalue in the return statement. It will either be returned via return-value-optimization (no copy, no move), or if the compiler decides it can not perform RVO, then it will use vector's move constructor to do the return. Only if RVO is not performed, and if the returned type did not have a move constructor would the copy constructor be used for the return.它们都不会复制,但第二个将引用被破坏的向量。命名右值引用在常规代码中几乎不存在。您可以像用 C++03 编写副本一样编写它。
但现在向量被移动了。在绝大多数情况下,类的用户不处理它的右值引用。
None of them will copy, but the second will refer to a destroyed vector. Named rvalue references almost never exist in regular code. You write it just how you would have written a copy in C++03.
Except now, the vector is moved. The user of a class doesn't deal with it's rvalue references in the vast majority of cases.
简单的答案是,您应该像编写常规引用代码一样为右值引用编写代码,并且在 99% 的时间里您应该以相同的方式对待它们。这包括有关返回引用的所有旧规则(即从不返回对局部变量的引用)。
除非您正在编写一个需要利用 std::forward 的模板容器类,并且能够编写一个采用左值或右值引用的通用函数,否则这或多或少是正确的。
移动构造函数和移动赋值的一大优点是,如果定义它们,编译器可以在 RVO(返回值优化)和 NRVO(命名返回值优化)调用失败的情况下使用它们。对于返回容器和物品等昂贵的物品来说,这是相当大的。从方法中有效地按值字符串。
现在,右值引用变得有趣的是,您还可以将它们用作普通函数的参数。这允许您编写具有 const 引用 (const foo& other) 和右值引用 (foo&& other) 重载的容器。即使参数太笨重而无法仅通过构造函数调用来传递,它仍然可以完成:
STL 容器已更新为几乎任何内容(哈希键和值、向量插入等)都具有移动重载,并且您将在其中最常见到他们。
您还可以将它们用于普通函数,如果您仅提供右值引用参数,则可以强制调用者创建对象并让函数执行移动。这更多的是一个示例,而不是真正的良好用途,但在我的渲染库中,我已为所有加载的资源分配了一个字符串,以便更容易在调试器中看到每个对象代表什么。接口是这样的:
它是一种“泄漏抽象”的形式,但允许我利用大多数时候必须创建字符串的事实,并避免再次复制它。这并不完全是高性能代码,但它是人们掌握此功能的可能性的一个很好的例子。这段代码实际上要求变量要么是调用的临时变量,要么是 std::move invoked:
或
或 ,
但这不会编译!
The simple answer is you should write code for rvalue references like you would regular references code, and you should treat them the same mentally 99% of the time. This includes all the old rules about returning references (i.e. never return a reference to a local variable).
Unless you are writing a template container class that needs to take advantage of std::forward and be able to write a generic function that takes either lvalue or rvalue references, this is more or less true.
One of the big advantages to the move constructor and move assignment is that if you define them, the compiler can use them in cases were the RVO (return value optimization) and NRVO (named return value optimization) fail to be invoked. This is pretty huge for returning expensive objects like containers & strings by value efficiently from methods.
Now where things get interesting with rvalue references, is that you can also use them as arguments to normal functions. This allows you to write containers that have overloads for both const reference (const foo& other) and rvalue reference (foo&& other). Even if the argument is too unwieldy to pass with a mere constructor call it can still be done:
The STL containers have been updated to have move overloads for nearly anything (hash key and values, vector insertion, etc), and is where you will see them the most.
You can also use them to normal functions, and if you only provide an rvalue reference argument you can force the caller to create the object and let the function do the move. This is more of an example than a really good use, but in my rendering library, I have assigned a string to all the loaded resources, so that it is easier to see what each object represents in the debugger. The interface is something like this:
It is a form of a 'leaky abstraction' but allows me to take advantage of the fact I had to create the string already most of the time, and avoid making yet another copying of it. This isn't exactly high-performance code but is a good example of the possibilities as people get the hang of this feature. This code actually requires that the variable either be a temporary to the call, or std::move invoked:
or
or
but this won't compile!
本身不是答案,而是指南。大多数时候,声明本地
T&&
变量没有多大意义(就像您对std::vector&& rval_ref
所做的那样)。您仍然需要std::move()
它们才能在foo(T&&)
类型方法中使用。还有一个已经提到的问题,当您尝试从函数返回这样的 rval_ref 时,您将得到标准的对被破坏的临时失败的引用。大多数时候我会采用以下模式:
您不持有任何返回临时对象的引用,因此您可以避免(缺乏经验的)程序员希望使用移动对象的错误。
显然,在某些情况下(尽管相当罕见),函数真正返回
T&&
,它是对可以移入对象的非临时对象的引用。关于 RVO:这些机制通常有效,编译器可以很好地避免复制,但在返回路径不明显的情况下(例外,
if
条件确定您将返回的命名对象,可能还有其他)是你的救星(即使可能更昂贵)。Not an answer per se, but a guideline. Most of the time there is not much sense in declaring local
T&&
variable (as you did withstd::vector<int>&& rval_ref
). You will still have tostd::move()
them to use infoo(T&&)
type methods. There is also the problem that was already mentioned that when you try to return suchrval_ref
from function you will get the standard reference-to-destroyed-temporary-fiasco.Most of the time I would go with following pattern:
You don't hold any refs to returned temporary objects, thus you avoid (inexperienced) programmer's error who wish to use a moved object.
Obviously there are (although rather rare) cases where a function truly returns a
T&&
which is a reference to a non-temporary object that you can move into your object.Regarding RVO: these mechanisms generally work and compiler can nicely avoid copying, but in cases where the return path is not obvious (exceptions,
if
conditionals determining the named object you will return, and probably couple others) rrefs are your saviors (even if potentially more expensive).