向量减小大小后如何不破坏元素两次?

时间:2019-07-05 20:06:32

标签: c++ resize stdvector destruction

出于测试目的,我试图创建自己的向量类,但无法弄清楚std::vector尺寸减小的工作原理。

class A
{
    A()
    { std::cout << "A constructed") << std::endl; }
    ~A()
    { std::cout << "A destroyed") << std::endl; }
}

main()
{
    std::vector<A> vec(3, A());
    vec.resize(2);
    std::cout << "vector resized" << std::endl;
}

输出为

A constructed       (1)
A constructed       (2)
A constructed       (3)
A destroyed         (1)
Vector resized
A destroyed         (2)
A destroyed         (3)

调用vec.resize(2)时,第三个元素被破坏,但向量的容量仍为3。然后,vec被破坏时,其所有元素(包括已被破坏的元素)也应被破坏。 std::vector如何知道他已经销毁了该元素?如何在我的向量类中实现它?

3 个答案:

答案 0 :(得分:2)

容量和大小之间有区别。给定一个std::vector<T> v;,向量已为v.capacity()个元素分配了内存。但是仅在前v.size()个地方包含构造的T对象。

因此,在空白向量上的v.reserve(1000)将不会调用任何其他构造函数。您的示例中的vec.resize(2)破坏了最后一个元素,vec[2]现在在内存中为空,但内存仍归vec所有。

我认为您的分配看起来像buffer = new T[newSize];。这不是std::vector的工作方式,不允许Ts没有默认构造函数。也许您没有意识到这一点,但是每当您获得一块内存时,它就已经包含对象,可以将其设为T x;甚至是new double[newSize];,它返回一个双精度数组(尽管它们的构造函数为空)。

只有一种方法可以获取C ++中可用的未初始化内存,即分配chars。这是由于strict aliasing rule.所致(也必须是)如何在此内存上显式调用构造函数,即如何在其中创建对象。向量使用称为placement new的东西来精确地做到这一点。这样,分配就是buffer = new char[newSize*sizeof(T)];,它不会创建任何对象。

对象的生存期由此放置位置new运算符和对析构函数的显式调用来管理。 emplace_back(arg1,arg2)可以实现为{new(buffer + size) T(arg1,arg2);++size;}。请注意,简单地buffer[size]=T(arg1,arg2);和UB是不正确的。 operator=期望左边的大小(*this)已经存在。

如果您想使用例如销毁对象pop_back,您必须 buffer[size].~T();--size;。这是应显式调用析构函数的少数几个地方之一。

答案 1 :(得分:1)

简单的答案是,vector通常通过使用inplace运算符new和operator delete方法来内部管理构造函数和析构函数调用。

弹出一个元素后,它会立即废弃,并且在内存仍然可用时,并且可能仍然包含一些剩余值,std :: vector本身知道哪些元素仍需要删除,因此不会调用析构函数再次。

答案 2 :(得分:1)

  

调用vec.resize(2)时,第三个元素被破坏,但向量的容量仍为3。

是的。 capacity是向量的内部数组可以物理容纳多少个元素。 size是该数组中实际有效的元素数。缩小size根本不会影响capacity

  

然后,vec被销毁时,其所有元素(包括已销毁的元素)也应被销毁。

先前已销毁并从数组中删除的第三个元素不会再次销毁。像您所想的那样,仅破坏了size个元素,而没有破坏capacity个元素。

  

std::vector如何知道他已经销毁了该元素?

它分别跟踪sizecapacity。从数组中删除一个元素时,其后的元素将分别在数组中向下移动1个插槽,并且size会减小。