从一个堆栈移动到另一个堆

Move from stack to heap

本文关键字:移动 另一个 堆栈 一个      更新时间:2023-10-16

我正在尝试调用一些函数,其中包括将元素添加到向量(按值传递的参数):

std::vector<Val> vec;   
void fun( Val v )
{
...
vec.push_back(std::move( v ));
...
}

问题是:移动语义有什么好处吗?

我对构造新的向量元素时变量将从堆栈"移动"到堆感到困惑:似乎它只是被复制。

移动而不是复制是否有任何好处实际上取决于Val的实现。

例如,如果Val的复制构造函数的时间复杂度为 O(n),但其移动构造函数的复杂度为O(1)(例如:它只包括更改几个内部指针),那么可能会有性能优势。


在以下Val实现中,移动而不是复制不会有任何性能优势:

class Val {
int data[1000];
...
};

但对于下面的一个,它可以:

class Val {
int *data;
size_t num;
...
};

如果 std::move 允许底层对象将指针从一个移动到另一个; 而不是(在复制中所做的)分配一个新区域,并复制可能是大量内存的内容。 例如

class derp {
int* data;
int dataSize;
}

data和dataSize可以在移动中交换;但副本可能要贵得多。

但是,如果你只有一个整数集合;移动和复制将相当于同一件事;除了对象的旧版本应该无效;无论这在该对象的上下文中应该意味着什么。

移动是对对象内部存储的盗窃(如果适用)。许多标准容器都可以从中移动,因为它们在堆上分配存储,这对于"移动"来说是微不足道的。在内部,发生的事情如下所示。

template<class T>
class DumbContainer {
private:
size_t size;
T* buffer;
public:
DumbContainer(DumbContainer&& other) {
buffer = other.buffer;
other.buffer = nullptr;
}
// Constructors, Destructors and member functions
}

如您所见,对象不会在存储中移动,"移动"纯粹是从容器otherthis的概念。事实上,这是优化的唯一原因是因为对象保持不变。

堆栈上的存储无法移动到另一个容器,因为堆栈上的生存期与当前范围相关联。例如,std::array<T>将无法将其内部缓冲区移动到另一个阵列,但如果T堆上有存储可以传递,则仍然可以有效地移动它。例如,从std::array<std::vector<T>>移动必须构造vector对象(array不可移动),但vector将廉价地将其托管对象移动到新创建的arrayvector

因此,来到您的示例,如果Val是可移动对象,则如下所示的函数可以减少开销。

std::vector<Val> vec;   
void fun( Val v )
{
...
vec.emplace_back(std::move( v ));
...
}

你不能摆脱构造vector,在容量填满时分配额外的空间,或者在vector内构造Val对象,但如果Val只是一个指向堆存储的指针,那么它可以在不实际窃取另一个向量的情况下尽可能便宜。如果Val不可移动,则不会通过将其转换为右值来丢失任何内容。这种类型的函数签名的好处是调用方可以决定他们是要复制还是移动。

// Consider the difference of these calls
Val v;
fun(v);
fun(std::move v);

第一个将调用Val的复制构造函数(如果存在),否则将无法编译。第二个将调用移动构造函数(如果存在),否则将调用复制构造函数。如果两者都不存在,则不会编译。