从技术上讲,如何在不复制的情况下移动函数返回值
C++ how is it technically possible to move a function return value without a copy?
当一个函数返回一个值时,这个值被放在堆栈上(函数堆栈帧被删除,但返回值保留在那里,直到调用者得到它)。
如果返回值在堆栈上,移动如何获得该值而不将其复制到可变内存位置?
例如:
A a = getA();
在许多c++实现中,返回"复杂"数据类型的函数被传递一个隐藏参数,该参数是指向返回实例所在空间的指针。本质上,编译器将Foo r = fun();
转换为
char alignas(Foo) r[sizeof Foo]; // Foo-sized buffer, unitialized!
fun(&r);
可以看到,Foo
是在调用方帧的堆栈上分配的。现在,在fun
的实施中可能会有一个副本。建设
Foo fun() {
Foo rv;
...
return rv;
}
通常实现为
void fun(Foo * $ret) {
Foo rv;
..
new ($ret) Foo(rv); // copy construction
}
当应用返回值优化时,它被更改为
void fun(Foo * $ret) {
Foo & rv = *(new ($ret) Foo);
...
return;
}
现在没有涉及到复制。
用于存储用于从函数返回值的临时值的存储(堆,堆栈,寄存器等)是实现定义的。你可以看到它是:
+-----------------------------+-------------------------+-----------------------------------------+
| target (caller stack frame) | temporary (unspecified) | return statement (function stack frame) |
+-----------------------------+-------------------------+-----------------------------------------+
值从右向左传递。还要注意,标准规定任何编译器都可以省略临时和赋值/复制/移动,并直接初始化目标。
写一个类,如:
class trace
{
public:
trace()
{
std::cout << "Init" << std::endl;
}
~trace()
{
std::cout << "Destroy" << std::endl;
}
trace( const trace& )
{
std::cout << "Copy init" << std::endl;
}
trace( trace&& )
{
std::cout << "Move init" << std::endl;
}
trace& operator=( const trace& )
{
std::cout << "Copy assign" << std::endl;
}
trace& operator=( trace&& )
{
std::cout << "Move assign" << std::endl;
}
};
和尝试不同的编译器优化是很有说明意义的。
假设A是聚合或原语。如果这是真的,那么yes move和copy语义是等价的。然而,如果A是像vector这样的复杂类型,那么它将包含指向资源的指针。当移动对象时,指针被复制而不复制它们所指向的值。
当你说"move"时,我假设你指的是c++ 11的move构造函数和std::move
函数。
移动一个对象实际上并没有移动整个对象。它使用它的move构造函数构造一个新的对象,该构造函数被允许获取原始对象持有的资源的所有权,而不是复制它们。例如,如果你写:
std::vector<int> foo = function_that_returns_a_vector();
编译器可以通过调用foo
的move构造函数并将函数返回的临时向量传递给它来实现这一点。move构造函数将获得临时向量指向其堆分配内容的内部指针的所有权,使临时向量为空。在c++ 11和move支持之前,foo
的copy构造函数会被调用,它会在堆上分配新的空间来复制返回向量的内容,即使该返回向量即将被销毁并且不再需要自己的副本。
请注意,编译器根本不必通过从返回的临时对象构造foo
来实现这一行。根据编译器特定于平台的调用约定,可以将(未初始化的)foo
变量的地址以这样一种方式传递给函数,即函数的返回值直接构造为foo
,从而避免在函数返回后进行复制。这就是所谓的复制省略。
最简单的方法是这样修改你的方法:
void getA(A& out);
A a;
getA(a);
但是,编译器会尽力避免这种多余的内容:
- 为什么不调用移动构造函数?(默认情况下只有构造器,没有别的)
- 如何在不复制的情况下将一个向量移动到另一个向量中
- C++ 移动语义是否在任何情况下都能节省资源?
- 如何在没有 std::move 的情况下移动临时对象
- 为什么C++默认情况下不移动构造右值引用?
- 为什么在这种情况下调用非常量右值移动构造函数?
- 在不中断引用的情况下移动静态库的 *.pdb 文件 - LNK4099
- QT QTest::键单击或鼠标移动似乎不适用于 QMenu 在我的情况下
- 如何在不妨碍RVO的情况下确保移动?
- 无法在不移动的情况下从函数返回 std::unique_ptr
- 默认情况下,返回是否使用移动或复制语义
- 在这种情况下,值是否会移动
- 为什么在这种情况下不调用我的移动构造函数?(首先分配 r 值引用并创建对象)
- 如果在没有move构造函数的情况下移动对象,会发生什么
- 在不影响其他对象的情况下移动对象(使用opengl)
- 默认/删除在存在RVO的情况下移动构造函数和赋值
- 对容器进行排序,然后在保留原始排序的情况下移动元素
- 在c++中不改变倒数第二位的情况下移动整数中的位
- 从技术上讲,如何在不复制的情况下移动函数返回值
- 如何在不移动整个场景的情况下移动一个2d对象?