2

出于测试目的,我试图创建自己的矢量类,但我无法弄清楚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知道他已经破坏了那个元素?如何在我的向量类中实现它?

4

3 回答 3

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. 这是由于严格的别名规则。还有(是|必须)一种方法如何在此内存上显式调用构造函数,即如何在那里创建对象。向量使用了一个叫做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=期望左边的 size( *this) 已经存在。

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

于 2019-07-05T20:18:03.467 回答
1

vec.resize(2)被调用时,第三个元素被销毁,但向量的容量仍然是 3。

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

那么当vec被销毁时,它的所有元素,包括已经被销毁的元素都应该被销毁。

先前被销毁并从数组中删除的第三个元素不会再次被销毁。像您想的那样,只有size元素数量被破坏,而不是元素数量。capacity

怎么std::vector知道他已经破坏了那个元素?

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

于 2019-07-05T21:50:52.787 回答
1

简单的答案是 vector 在内部管理构造函数和析构函数调用,通常使用就地 operator new 和 operator delete 方法。

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

于 2019-07-05T20:38:41.873 回答