【问题标题】:STL container for region/arena-like allocation用于类似区域/竞技场的分配的 STL 容器
【发布时间】:2015-09-20 06:56:29
【问题描述】:

我正在编写一个类(一个 Huffman 编码器,如果您好奇的话),它需要包含一个专门的二叉树,而 STL 容器并不直接适用于该二叉树。用一些左/右指针定义一个TreeNode 结构并构建它是没有问题的。我的问题涉及节点本身的分配。

我认为如果我使用 STL 容器作为我的“分配器”而不是直接为每个节点调用 new 会非常好。这很有吸引力,因为它会使破坏变得微不足道:我不必在析构函数中遍历我的树来单独 delete 所有节点;我用作“分配器”的 STL 容器的库存析构函数会自动完成。

我对“分配器”的第一选择是std::vector<TreeNode>。但这根本不起作用,因为当然随着向量的增长,它必须重新分配自己,这会使我的所有指针无效。 所以我目前正在做的是坚持std::vector 的想法并放弃指针;我曾经有一个指针的每个地方都有一个整数,而不是在我的向量中保存一个索引。 也就是说,在我的父类中,我有类似的成员

std::vector<TreeNode> _heap;        // all nodes in tree
int _root;                          // index into _heap

然后要分配一个新节点,我会这样做:

TreeNode newnode(/*whatever*/);
nodei = _heap.size();
_heap.push_back(newnode);
// now do something with nodei

事实上,这一切都很好,除了:使用索引而不是指针是一个简单的麻烦。我通常在任何地方都将指向节点引用为*nodep,我必须改用_heap[nodei]。 (但是,它再次起作用;这不是问题。)

我的问题是:

  1. 是否有一个 STL 容器类保证其元素以后不会移动,这样指向它们的指针将在容器的生命周期内保持有效?
  2. 指向 STL 容器中的元素的指针是个好主意吗?
  3. 对这类事情(即指向容器化堆中的元素)使用迭代器或引用而不是指针会更好吗?
  4. 是否有更好的方法来实现具有简单(即隐式)销毁语义的本地堆分配器?

我对其他想法持开放态度,尽管我应该警告你我相当保守。我想坚持在“基线”C++ 中工作的东西,即我会回避只在 C++11 或其他东西中使用的东西。而且我特别担心 sigc 和 boost 等包的开销和复杂性,所以我也不太可能想要使用它们的特殊容器或分配器之一。

【问题讨论】:

  • std::list 应该适用于这种情况。
  • 虽然它是 C++11(或 boost),但像 std::unique_ptr 这样的智能指针会为您提供自动销毁。将节点存储在std::vector&lt;std::unique_ptr&lt;TreeNode&gt;&gt; 中。如果 C++11 绝对不适合你,你当然可以实现自己的 unique_ptr 版本。

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


【解决方案1】:

是否有一个 STL 容器类保证其元素以后不会移动,这样指向它们的指针将在容器的生命周期内保持有效?

如果您只移动push_back/emplace_back 元素,std::deque 不会移动元素,这足以完成您的任务,您可以获取指向元素的指针。

此外,std::deque 的最流行实现以块的形式分配内存,其中一个块可以顺序存储多个元素 - 这提高了局部性,并减少了分配次数(与 std::list 相比)。

指向 STL 容器中元素的指针是个好主意吗?

只要您了解底层语义和保证(您可以在 C++ ISO 中阅读),这是个好主意。

例如,如果您不更改 std::vector 的容量并且不缩小它的大小 - 指向向量中元素的指针就可以了。

对这类事情(即指向容器化堆中的元素)使用迭代器或引用而不是指针会更好吗?

除了某些 STL 实现可能会在 Debug 构建中提供额外的防御性检查之外,我没有看到任何好处。

有没有更好的方法来实现本地堆分配器,具有简单(即隐式)破坏语义?

实际上,我喜欢他们使用索引的想法,即 region[node_index] 就像您所做的那样(尽管在代码期望“真实”指针的情况下并不总是可以接受)。我看到这种方法有几个潜在的好处:

  • std::vector 呈指数增长,导致O(log N) 分配给N 元素。可以获得类似的指针属性,但它更复杂 - 您应该维护不同大小的块列表,例如std::vector&lt;std::vector&lt;Node&gt;&gt;,其中每个新块的容量为 total_size * k,并且您应该确保不要更改它(因为指向元素的指针)。

  • 您可以控制sizeof(index) - 您可以使用适合您任务的索引类型,例如它可以是 1-2 个字节。但是指针的大小可能要大得多,尤其是在 x64 上 - 特大 8 字节。

【讨论】:

  • 感谢您的回答。我喜欢使用 std::deque 的想法;我试试看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-11
  • 1970-01-01
  • 2012-03-01
  • 2011-02-27
  • 2011-07-14
  • 2011-12-24
相关资源
最近更新 更多