为什么std::vector::emplace在没有调用任何复制构造函数的情况下调用析构函数?

3

我正在使用 std::vector 来存储对象,希望尽可能避免调用析构函数。
我用移动构造函数和赋值运算符替换了复制构造函数和赋值运算符:

class Object
{
    Object(const Object&) = delete;
    Object(Object&&);
    Object& operator=(const Object&) = delete;
    Object& operator=(Object&&);
    [...]
};

我是这样初始化它的:

std::vector<Object>   container;

container.reserve(42) // Reserve a lot in order to be sure it won't be a problem

然后,我使用emplace_back添加了两个元素(构造函数接受一个int参数):

container.emplace_back(1);
container.emplace_back(3);

到目前为止,一切都很好。但是我想在最后一个元素之前使用emplace插入一个元素:

auto it = container.end();

it--; // Last position.
it--; // Before last position.
container.emplace(it, 2);

但是这里调用了一个析构函数。

我试图通过Valgrind定位原因,似乎emplace函数调用了_M_insert_aux,导致我的析构函数被调用。

我该如何避免这种情况?


3
你打算如何做到这一点,而又不至少调用一次析构函数? - T.C.
3
emplaceemplace_back 不同。emplace_back 只是构造一个新元素,而 emplace 则必须处理指定位置已经构造好的元素。 - songyuanyao
@T.C. 这恰好是我的问题... - Aracthor
1个回答

2

你无法避免这种情况。这就是 vector 的工作原理。它是连续的数组。你唯一能在连续数组中插入新元素的方法是将旧元素移动到下一个位置。这意味着使用移动赋值将它们移动到新位置。

如果你有以下 vector 和它们的内容:

[5][12][16]

如果您在第二个元素之后插入,则最终会得到以下结果:
[5][12][*][16]

当“*”是移动自一个元素的值时。

然后来到emplaceemplace的作用是在原地显式构造值;这就是它的作用。 然而,在第三个元素中已经有一个活动对象:被移动的值。

因此,在新对象可以代替旧对象构造之前,必须销毁此对象。 因此必须调用析构函数。

如果您使用的是insert而不是emplace,则仍将调用析构函数。 但那将是您传递给insert函数的对象的析构函数。

因此,会有“额外”的析构函数某处被调用。

但实际上,您不应该担心析构函数的数量。 关注绝对成本。一般来说,如果您有一个仅移动类型,则移动自一个值的析构函数将很便宜。


我的对象在构造时使用new[]分配一个元素,并在析构时使用delete[]销毁它。由于堆分配可能会很耗费资源,我想知道如何尽量避免它。但也许过度避免也不是好事。 - Aracthor
2
这就是移动操作的作用。您的类应该将new[]分配的数组所有权从被移动对象传递到移动目标对象。这样可以消除在被移动对象的析构函数中delete[]数组的需要,以及在移动目标对象中new[]新数组的需要。 - Miles Budnek
@MilesBudnek 所以我只需要将原始对象的指针设置为 nullptr,就不会删除它了... 感谢您的帮助。 - Aracthor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接