出于测试目的,我试图创建自己的向量类,但无法弄清楚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
如何知道他已经销毁了该元素?如何在我的向量类中实现它?
答案 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
如何知道他已经销毁了该元素?
它分别跟踪size
和capacity
。从数组中删除一个元素时,其后的元素将分别在数组中向下移动1个插槽,并且size
会减小。