英文:
How do you move a local variable from a function in C++?
问题
我理解C++11引入了移动操作,它们本质上是所有权的转移。与此相关的是右值(rvalue)的概念,这意味着如果你需要计算临时值然后进行复制,那么复制操作将会非常昂贵。就像这样:
std::map<std::string, std::string> a, b;
std::map<std::string, std::string> c = a + b;
// 我知道对于map来说加法没有意义,只是一个例子。
因此,编译器首先创建一个临时的map来存储结果,然后通过复制构造函数来构建c
map。
使用移动操作,你可以直接将c
获取临时map的所有权!
到这里我理解得差不多了,但是我不明白的是当涉及到从函数返回非堆内存成员时。对于具有堆内存资源的元素,我们可以从函数中将所有权转移给外部,避免复制,比如字符串(string)。但对于数据成员来说,它们是局部的,不拥有堆上的资源,这种情况下如何理解移动操作呢,就像:
int example()
{
int a = 7;
return a;
}
int main()
{
std::vector<int> temp;
for (int i = 0; i < 20; ++i)
{
temp.push_back(example());
}
}
在这种情况下,如何移动一个局部变量呢?
英文:
I understand that C++11 has move operations which are essentially a transfer of ownership. Linked with it is the concept of the rvalue, so that means that if you need to compute temporary values and then copy it, that copy would be very expensive. Like
std::map<string, string> a, b;
std::map<string, string> c = a+b;
// I know that addition doesn't make sense for the maps but just an example.
So, the compiler first creates a temp map to store the result and then copy constructs the c
map out of it.
With move, you can just give the c ownership of this temporary map!
That much I understand, however, I fail to understand when it comes to non-heap members being returned from the functions. For elements which has heap based resources, we can transfer the ownership from the function to the outside and avoid copy, like string. But for data members which are local and don't own something on the heap how does the move make sense in that case? like:
int example()
{
int a= 7;
return a;
}
int main()
{
std::vector<int> temp;
for (int i=0; i<20; ++i)
{
temp.push_back(example());
}
}
How does it make sense to move a local variable in this case?
答案1
得分: 3
这是一个常见情况,普通人对移动语义的描述经常会引起混淆。
C++11引入了移动操作,它们本质上是所有权的转移
尽管经常被描述为“所有权的转移”,但这只是移动语义的效果的描述,当这个“所有权的转移”实际上对于被移动的对象有意义时。
但如果没有有意义的“所有权的转移”,那么移动实际上就会变成普通的复制,所以:
在这种情况下移动局部变量有意义吗?
也许有,也许没有。这取决于情况。基于堆的对象管理是移动语义“有意义”的最常见用例。
也许理解移动对象意味着等同于复制对象是最好的方法,但被移动的对象可能最终处于某种“有效但未指定状态”(这是C++库的移动语义规范中指定的措辞,但通常被接受为对移动语义的描述,总的来说)。
通常情况下,当你复制一个对象时(除非对象有奇怪的operator=
重载),期望复制前的对象保持不变,并且期望复制后的对象最终成为复制前对象的逻辑副本。
移动唯一做的事情就是添加一个属性,即被移动的对象最终处于某种“有效但未指定状态”。
就是这样。
这就是移动的全部内容。
对于容器的经典示例,容器的内容在堆上,移动等同于交换一些指针和辅助元数据。但是,直接回答你的问题:
在这种情况下移动局部变量有意义吗
答案是:这取决于变量是什么。对于某些复杂的类来说,复制复制的对象可能需要更多的工作。但如果一个对象被移动,那么它是一种成本较低的操作,将被移动的对象留在某种“有效但未指定状态”。
这里没有要求对象包含堆上的任何内部数据。
一个低级示例:对象表示一个文件,以一个简单的int
(实际的文件名可能只是这个整数值,作为固定文件名的一部分)标识。复制对象需要复制整个底层文件。但是,移动这个假设的对象仅需要将被移动的对象设置为逻辑等同于/dev/null
。因此,在这里移动“局部变量”可能实际上有意义。
英文:
This is a case where the common, layman's description of move semantics often creates confusion.
> C++11 has move operations which are essentially a transfer of ownership
Although often described as a "transfer of ownership", this is just a description of the effects of move semantics when this "transfer of ownership" actually means something for the object being moved.
But if there is no meaningful "transfer of ownership" then a move effectively devolves into an ordinary copy, so:
> it make sense to move a local variable in this case?
Maybe, maybe not. It depends. Heap-based object management is the most common use case where move semantics "means something".
Perhaps the best way to understand what moving an object means, is to consider a move to be equivalent to copying an object, but the moved-from object may end up in some "valid, but unspecified state" (this is the wording specified for the C++ library's move semnatics, but it is accepted to be a description of move semantics, in general).
Normally, when you copy an object (unless the object has a weird operator=
overload) the copied-from object is expected to be unchanged, and the copied-to object is expected to end up being a logical copy of the copied-from object.
The only thing that a move does, is add an additional property that the moved-from object ends up in some "valid, but unspecified state".
That's it.
That's all that a move is.
For a classical example of a container, with the container's contents on the heap, a move is equivalent to swapping some pointers and ancillary metadata. But, to answer your question directly:
> it make sense to move a local variable in this case
The answer is: it depends on what the variable is. It can certainly be possible that, for some complicated class, a lot more work is required to create a duplicate copy of a copied-from object. But, if an object is being moved, it is a less-expensive operation, leaving in the moved-from object in some "valid, but unspecified state".
Nothing here requires the object to contain any internal data on the heap.
A lame example: object represents a file, identified as a simple int
(the actual filename might merely be this integer value, as varying part of a fixed filename). Copying an object requires the underlying file to be copied in its entirety. But, moving this hypothetical object requires merely the moved-from object to be set to be the logical equivalent of /dev/null
. So, moving a "local variable", here, might actually mean something.
答案2
得分: 2
编译器可以自由分配空间(或使用已分配的空间)用于函数的“返回”,只需让函数知道(缺乏更好的词汇)返回不在堆栈上而在“这里”。
一切都归结为调用-被调用者协议,当编译器负责两者时,它可以进行各种优化,包括“拷贝省略”或“返回值优化”(我认为你在提问中可能是在指 elision,而不是实际的 std::move()
语义)。但在本讨论的目的上,这两者是密切相关的。
英文:
The compiler is free to allocate the space (or use an already allocated space) for a "return" from a functions and just "let the function know" (for the lack of better word) that the return is not on a stack but "over here".
It all just comes down to call-callee agreement and when the compiler is in charge of both it can do all sorts of optimizations, including the "copy elision" or "return value optimization" (I believe you may be referring to elision in your question, not the actual std::move()
semantic). But the two are closely related for the purposes of this discussion.
答案3
得分: 2
对于 int
,移动和复制是相同的操作。只有当类型(或者包含在其中的类型,例如结构中的数据成员)显式定义了比复制更优化的移动操作时,类型才具有更优化的移动(例如 std::vector
、std::string
、std::map
等)。
英文:
For int
move and copy are the same operation. A type has a move that is more optimized than a copy only if that type (or a type contained in it (e.g. a data member in a struct) explicitly defines a move operation that is more optimal (e.g. std::vector
, std::string
, std::map
etc).
答案4
得分: 0
I think this answer nicely explains why moving is not equivalent to transfer of ownership. 我认为这个回答很好地解释了为什么移动不等同于所有权的转移。
I totally agree with that, because what you do is a move, and that maybe some ownership is transfered is an implementation detail. 我完全同意这一点,因为你所做的是移动,也许会有一些所有权被转移是一个实现细节。
I want to take this one step further by showing you an example where transfer of ownership of a resource is made that is not allocated memory. The resouce can be eg a file handle: 我想进一步举例说明,展示一个转移资源所有权的例子,而不是分配内存。资源可以是文件句柄,例如:
struct foo {
std::ofstream out;
};
Nothing allocated on the heap, at least not directly (If you are pedantic and argue that std::ofstream
may internally use dynamic allocation, replace it with a FILE*
for the sake of the discussion). The point is that foo
is not just about allocating and deleting some memory, but about claiming and releasing a resource. 没有在堆上分配任何东西,至少不是直接的(如果你很挑剔并且认为std::ofstream
可能在内部使用动态分配,为了讨论的目的,用FILE*
代替它)。关键是,foo
不仅仅是分配和删除一些内存,而是声明和释放资源。
You cannot copy a foo
because it manages a resource that cannot be copied. You can move a foo
. Whether moving allocates memory is secondary. 你不能复制一个foo
,因为它管理着一个不能被复制的资源。你可以移动一个foo
。移动是否分配内存是次要的。
In your example:
int example()
{
int a= 7;
return a;
}
Copy elision is relevant. 在你的例子中:
int example()
{
int a= 7;
return a;
}
拷贝省略是相关的。
When you move an int
it is copied. Nevertheless you can move an int
like you can move other types. Having something dynamically allocated is not a precondition to be moved. 当你移动一个int
时,它被复制。然而,你可以像移动其他类型一样移动一个int
。拥有动态分配的内容不是移动的先决条件。
英文:
I think this answer nicely explains why moving is not equivalent to transfer of ownership. I totally agree with that, because what you do is a move, and that maybe some ownership is transfered is an implementation detail.
I want to take this one step further by showing you an example where transfer of ownership of a resource is made that is not allocated memory. The resouce can be eg a file handle:
struct foo {
std::ofstream out;
};
Nothing allocated on the heap, at least not directly (If you are pedantic and argue that std::ofstream
may internally use dynamic allocation, replace it with a FILE*
for the sake of the discussion). The point is that foo
is not just about allocating and deleting some memory, but about claiming and releasing a resource.
You cannot copy a foo
because it manages a resource that cannot be copied. You can move a foo
. Whether moving allocates memory is secondary.
In your example
int example()
{
int a= 7;
return a;
}
Copy elision is relevant.
When you move an int
it is copied. Nevertheless you can move an int
like you can move other types. Having something dynamically allocated is not a precondition to be moved.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论