我一直试图找出一种管理动态数组的有效方法,我可能偶尔会改变它,但是想要经常随机访问和迭代。
我希望能够:
所以为了实现这一点,我一直在尝试使用std::vector<T>::iterator
,并且它工作得非常好,直到最近,当我调整向量(例如调用push_back()
)时我正在存储迭代器的。所有迭代器都变得无效,因为它们指向过时的内存。
是否有任何有效的(可能是STL-)方法来保持迭代器指针的最新状态?或者我是否必须手动更新每个迭代器?
这整个方法是否值得?我应该坚持使用指数吗?
编辑: 我之前使用过指数并且没问题,但是我改变了我的方法,因为它仍然不好。我总是要把整个数组拖到范围内,索引可以很容易地用于任何数组。也没有完美的方法来定义“NULL”索引(我都不知道)。
更新所有指针以及调整大小操作的选项怎么样?您所要做的就是存储原始vector::begin
,调整vector
的大小,然后更新所有指向vector.begin() + (ptr - prevBegin)
的指针,调整操作已经是您应该避免的事情。
答案 0 :(得分:5)
完全实现所有3个目标是不可能的。如果你是完全连续的,那么你有一个有限大小的内存块,获得更多内存的唯一方法是要求更多的内存,这将与你已有的内存不相连。所以你必须至少在某种程度上牺牲至少一个要求:
std::deque
。这是一种数组数组结构。它不会使引用无效,因为我认为任何增加其大小的操作。它取决于您的数据类型的详细信息,但通常它的性能对于连续数组而言比链表非常接近。做得好但是旧的(5年)基准:https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html。另一种选择是编写一个分块分配器,以便与deque
或其他结构一起使用。这是相当多的工作。vector
vector
并且永远不会调整大小。除非你有充分的理由,否则我会坚持使用指数。如果你的主要性能瓶颈是与大量元素相关的迭代(正如你的连续性要求所暗示的那样),那么整个索引的东西应该是一个非问题。如果你确实有一个很好的理由来避免索引(你还没有说明),那么我会在主循环操作中分析deque
与vector
,以查看{{更差] 1}}确实如此。它可能勉强更糟,如果deque
和deque
都不能很好地为您工作,那么下一个替代方案是相当多的工作(可能涉及分配器或自定义数据结构)。
答案 1 :(得分:4)
如果您可以使用以下数据结构,则根据您的需要:
std::vector<std::unique_ptr<Foo>>
然后无论向量如何调整大小,如果通过Foo*
访问数据,指向foo的指针都不会失效。
当你需要在向量中存储的Foos
的数量发生变化时,向量可能需要调整其内部连续内存块的大小,这意味着你指向了向量内的任何迭代器将失效。
(您可以在C ++ 0x迭代器失效规则上阅读更多here)
但是,由于存储在向量中的对象是指向堆中其他位置的对象的指针,因此指向对象(在此示例中为Foo
)将不会失效
请注意,向量拥有 Foo
(由std::unique_ptr<Foo>
提及),而您可以存储非拥有指针{ {1}}保留Foo
作为访问Foo*
数据的方法。
只要该向量超过您通过Foo
访问Foo
的需要,那么您将不会遇到任何终身问题。
因此,就您的要求而言:
是,Foo*
实现此目标
是,存储std::vector
作为单独访问每个元素的方法,并且保持独立于数组句柄(Foo*
)
是,vector::iterator
自动完成此操作,在您需要时自动调整大小。
<强>加成:强>
在向量中使用智能指针(在此示例中为std::vector
)意味着内存管理也会自动为您处理。 (确保在矢量被销毁后,您不会尝试访问std::unique_ptr
。
修改强>
在评论中已经指出,在向量中存储Foo*
违反了您要求将对象存储在连续内存中的要求(如果确实这就是将数组存储在连续内存,因为向量的基础数组将连续,但访问std::unique_ptr<Foo>
对象将导致间接)。
但是,如果您为Foo
和vector
对象使用合适的分配器(例如竞技场分配器),那么您将更有可能遭受更少的缓存未命中,就像您的{{ 1}}对象将存在于Foo
使用的内存附近,因此在迭代向量时有更高的机会进入缓存。