C ++如何管理连续动态数组的迭代器

时间:2017-08-24 18:01:34

标签: c++ arrays pointers memory-management stl

我一直试图找出一种管理动态数组的有效方法,我可能偶尔会改变它,但是想要经常随机访问和迭代。

我希望能够:

  • 将数组存储在连续数据块中(减少缓存未命中)
  • 单独且独立于数组句柄(指针>索引)
  • 访问每个元素
  • 调整阵列大小(动态)

所以为了实现这一点,我一直在尝试使用std::vector<T>::iterator,并且它工作得非常好,直到最近,当我调整向量(例如调用push_back())时我正在存储迭代器的。所有迭代器都变得无效,因为它们指向过时的内存。

是否有任何有效的(可能是STL-)方法来保持迭代器指针的最新状态?或者我是否必须手动更新每个迭代器?

这整个方法是否值得?我应该坚持使用指数吗?

编辑: 我之前使用过指数并且没问题,但是我改变了我的方法,因为它仍然不好。我总是要把整个数组拖到范围内,索引可以很容易地用于任何数组。也没有完美的方法来定义“NULL”索引(我都不知道)。

更新所有指针以及调整大小操作的选项怎么样?您所要做的就是存储原始vector::begin,调整vector的大小,然后更新所有指向vector.begin() + (ptr - prevBegin)的指针,调整操作已经是您应该避免的事情。

2 个答案:

答案 0 :(得分:5)

完全实现所有3个目标是不可能的。如果你是完全连续的,那么你有一个有限大小的内存块,获得更多内存的唯一方法是要求更多的内存,这将与你已有的内存不相连。所以你必须至少在某种程度上牺牲至少一个要求:

  • 如果您愿意部分牺牲连续性,可以使用std::deque。这是一种数组数组结构。它不会使引用无效,因为我认为任何增加其大小的操作。它取决于您的数据类型的详细信息,但通常它的性能对于连续数组而言比链表非常接近。做得好但是旧的(5年)基准:https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html。另一种选择是编写一个分块分配器,以便与deque或其他结构一起使用。这是相当多的工作。
  • 如果您可以使用索引,那么您只需使用vector
  • 即可
  • 如果您不需要调整大小,您仍然可以使用vector并且永远不会调整大小。

除非你有充分的理由,否则我会坚持使用指数。如果你的主要性能瓶颈是与大量元素相关的迭代(正如你的连续性要求所暗示的那样),那么整个索引的东西应该是一个非问题。如果你确实有一个很好的理由来避免索引(你还没有说明),那么我会在主循环操作中分析dequevector,以查看{{更差] 1}}确实如此。它可能勉强更糟,如果dequedeque都不能很好地为您工作,那么下一个替代方案是相当多的工作(可能涉及分配器或自定义数据结构)。

答案 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*实现此目标

  • 单独且独立于数组句柄(指针&gt;索引)
  • 访问每个元素

,存储std::vector作为单独访问每个元素的方法,并且保持独立于数组句柄(Foo*

  • 调整阵列大小(动态)

vector::iterator自动完成此操作,在您需要时自动调整大小。

<强>加成:

在向量中使用智能指针(在此示例中为std::vector)意味着内存管理也会自动为您处理。 (确保在矢量被销毁后,您不会尝试访问std::unique_ptr

修改

在评论中已经指出,在向量中存储Foo*违反了您要求将对象存储在连续内存中的要求(如果确实这就是将数组存储在连续内存,因为向量的基础数组连续,但访问std::unique_ptr<Foo>对象将导致间接)。

但是,如果您为Foovector对象使用合适的分配器(例如竞技场分配器),那么您将更有可能遭受更少的缓存未命中,就像您的{{ 1}}对象将存在于Foo使用的内存附近,因此在迭代向量时有更高的机会进入缓存。