我一直在大量使用std::vector
,最近我问了自己一个问题:" std::vector
是如何实现的呢?"
我有两种选择:
1)使用链表,然后让API感觉像是随机访问(例如重载operator[]
)。
2)使用new
,例如Foo * temp = new Foo [20]
:我相信他们做了类似这样的事情,但这又引起了另一个问题。他们是否总是分配一个最大(uint32_t
)存储来实现随机访问?(这在内存方面是低效的。)
还是应该注意其他什么事情?
通过使用底层数组实现。
无法使用链表来实现std::vector<T>
,因为标准保证列表中的元素将在连续的内存中保存。
我相信第三种方法是正确的。不能只使用new T[n]
,因为它实际上需要构造与分配的对象数量相同的对象,例如
std::vector<Foo> v;
v.reserve(10);
new Foo[10]
,那么您只是创建了10个Foo实例。push_back
对象时)使用placement new将复制构造的实例放置到其保留中的正确内存位置,并使用显式调用析构函数将它们移除(这是仅与placement new结合使用的情况)。分配器类为此提供以下方法,我假设vector的实现会使用它们。 void construct(pointer p, const_reference val);
Returns:
new((void *)p) T(val)
void destroy(pointer p);
Returns:
((T*)p)->~T()
没有一种实现方式是固定的。不同的实现可以不同,只要保留语义并满足要求即可。
在任何给定时间,必须有一个基本类型为 T 的数组来满足连续性的要求。但是,如何分配、增长、缩小和释放它取决于实现者。
你可以自己阅读实现代码,它就在头文件中。
我可以告诉你,没有实现使用链表。它们与标准的要求不一致。
operator new()
、operator delete()
和类的构造函数/析构函数。除了重新调整步骤公式之外,请列举两个实现之间的一个潜在差异。 - Potatoswatter<T
>进行内存管理。在这种情况下,new运算符不够灵活,因为它既分配又初始化内存。编辑:另外,我刚刚发现Herb Sutter的博客文章,其中他评论了Andrew Koenig早期的博客文章,关于是否应该担心向量元素在内存中是连续的: 不用惊慌:向量保证是连续的。
我相信STL使用选项#2(或类似选项),因为std::vector<>保证将元素存储在连续的内存中。
如果你正在寻找一个不需要使用连续内存的内存结构,请查看std::deque。
在任何体面的实现中,实际上根本没有数组(如果有的话,你不能在其中使用任何对象而不带默认构造函数),只有被分配的原始内存。它的分配方式通常是每次需要扩展时翻倍。
然后,向量使用就地分配来调用类的构造函数,以便在每个插槽实际使用时将其放置在正确的位置。
当需要扩展时,它会尝试就地重新分配(但这有点愚蠢,通常不起作用,想想Windows 98堆压缩),但通常最终会进行全新的分配并复制过去。
标准STL向量总是在一起的,但并非所有实现都像那样工作(我知道,因为我写过其中一些)。可能没有一个完全是链表,但也不是全部都是。
reserve()
函数的功能和向量元素必须连续的要求,我认为下面是可能实现vector
的一种方式。
capacity()
方法:http://www.cplusplus.com/reference/stl/vector/capacity/ - Mike DeSimone