C++中什么是真正的空std::vector?

3
在类A中我有两个包含其他类B和C对象的向量。我知道这些向量最多可以容纳多少元素。在类A构造函数的初始化列表中,我将这些向量初始化为它们的最大大小(常数)。
如果我理解正确的话,现在我有一个使用其默认构造函数初始化的类B对象的向量。是这样吗?当我编写这段代码时,我认为这是唯一的处理方法。然而,我后来了解了std::vector.reserve(),我想实现不同的目标。
我希望为这些向量分配尽可能多的内存,因为添加到它们的内容由用户输入控制,所以我不希望经常调整大小。但是,我要每秒迭代很多次这个向量,而且我只处理已标记为“活动”的对象。每次迭代都检查类B/C的布尔成员变量是愚蠢的。我甚至不希望我的迭代器看到这些对象存在。
提前保留最大空间并使用push_back向向量添加新对象是解决此问题的方法吗?

3
您是否已经确定可能存在性能问题以及该问题很可能存在? - David Thornley
@DavidThornley 这不仅是一个性能问题,而是一个基本的C++问题。默认情况下应该永远不创建无效但存在的对象。你不必测量从“错误的方式”转换为“正确的方式”的性能来证明切换的合理性;你必须测量它来证明不切换的合理性。 - David Stone
3个回答

12
一个向量具有容量和大小。容量是已分配内存的元素数量。大小是实际在向量中的元素数量。当向量的大小为0时,它为空。因此,size()返回0,empty()返回true。这并不意味着此时向量的容量(这将取决于自创建以来对向量进行的插入和删除次数等因素)。capacity()会告诉您当前容量-即在必须重新分配其内部存储以容纳更多元素之前,向量可以容纳的元素数量。
因此,当您构造一个vector时,它具有特定的大小和特定的容量。默认构造的vector的大小为零,容量由实现定义。您可以自由地向vector中插入元素,而不必担心vector是否足够大 - 直到max_size() - max_size()是该系统上vector可以具有的最大容量/大小(通常足够大,无需担心)。每次将项插入vector时,如果它具有足够的容量,则不会向vector分配内存。但是,如果插入该元素会超过vector的容量,则vector的内存会在内部重新分配,以便它具有足够的容量来容纳新元素以及实现定义的一些新元素(通常情况下,vector的容量可能会增加一倍),并将该元素插入向量中。这发生在常数摊销时间内,因此通常不需要担心它会成为性能问题。
如果您发现自己经常向vector添加元素,导致多次重新分配内存,这可能会影响性能,那么可以调用reserve()将容量设置为至少给定值。通常情况下,当您非常确定vector可能包含多少个元素时,才会这样做。但是,除非您知道这会影响性能,否则这可能是一个不好的主意。这只会使您的代码更加复杂。而恒定摊销时间通常足以避免性能问题。
您还可以像您提到的那样构造具有给定数量默认构造元素的vector,但是,除非您真的需要这些元素,否则这将是一个不好的主意。 vector应该使您无需担心在插入元素时重新分配容器(就像您必须使用数组一样),并且为了分配内存而在其中默认构造元素将会破坏这一点。如果您确实想要这样做,请使用reserve()。但同样,除非您确定这将改善性能,否则不要费心使用reserve()。正如另一个答案中指出的那样,如果您根据用户输入向vector插入元素,则很可能I/O的时间成本远远超过当它耗尽容量时重新分配vector内存的时间成本。
与容量相关的函数:
capacity()  // Returns the number of elements that the vector can hold
reserve()   // Sets the minimum capacity of the vector.

与大小相关的函数:

clear()  // Removes all elements from the vector.
empty()  // Returns true if the vector has no elements.
resize() // Changes the size of the vector.
size()  // Returns the number of items in the vector.

感谢您的精彩回答。 :) 我学到了比我直接问的更多,这就是我喜欢 Stack Overflow 的原因。 - RyanG
1
+1,但使用reserve的另一个原因是:如果插入元素,则指向向量中元素的迭代器、引用和指针将失效,除非您使用push_back并且vec.size() < vec.capacity()。因此,在某些情况下,如果您正在循环中添加元素并拥有保留的迭代器,则首先调用reserve()可能会很有用。 - rlbond
我刚刚通过一次艰难的经验发现erase()函数也是如此。 - RyanG
一般来说,任何改变vector而不是vector中的元素的操作都可能使该vector的所有迭代器无效。因此,在迭代vector或需要保存任何迭代器到它时,通常不建议修改vector。但是,某些操作(例如erase())会返回一个有效的迭代器到下一个元素,因此在这种情况下可以修改vector并仍然具有有效的迭代器,但必须小心处理你正在做的事情,否则可能会自食其果。 - Jonathan M Davis

4

是的,reserve(n)会分配空间,但不会实际放置元素-增加capacity()而不增加size()

顺便说一下,如果“由用户输入控制添加”意味着用户点击“插入X”,您将X插入向量中,则无需担心调整大小的开销。等待用户输入比摊销常数调整大小性能慢得多。


1

你的问题有点令人困惑,让我试着回答一下我认为你问的是什么。

假设你有一个默认构造的 vector<B>。然后你调用了 vec.reserve(100)。现在,vec 包含 0 个元素。它是空的。 vec.empty() 返回 truevec.size() 返回 0。每次调用 push_back,你将插入一个元素,除非 vec 包含 100 个元素,否则不会进行重新分配。


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