4

假设我想构建一个向量容器,与 std::vector 不同,它允许未初始化的存储。容器的用法,比如说vec <T>,大概是这样的:

  • 用户明确指出向量应该分配 N 个未初始化的元素,如下所示:

    vec <T> a(N, no_init);

  • 在数据已知的某个时刻,用户n使用参数显式初始化位置处的元素args...

    a.init(n, args...);

  • 或者,等效地,手动构造元素:

    new (&a[n]) T(args...);

  • 其他操作可能会更大规模地初始化或复制(如std::uninitialized_copy),但这只是为了方便;基本的底层操作是相同的。

  • 完成某些任务后,向量可能会留下一些已初始化的元素,而另一些则没有。该向量不包含任何额外信息,因此最终,在释放内存之前,它要么销毁所有元素,要么仅根据T.

我很确定这是可以做到的,只是我不确定后果。自然地,我们希望这个结构对所有类型都是安全的,T假设用户在构造它之前没有尝试使用未初始化的元素。这听起来像是一个强有力的假设,但仅访问向量范围内的元素并没有太大的不同,而且它很常见。

所以我的问题是:

  1. 对于哪些类型,允许这种未初始化的操作是安全的vec <T> a(no_init)?我想is_pod会没问题,而且很可能is_trivial也是如此。我不想施加不必要的限制。

  2. 应该始终执行销毁还是仅针对某些类型执行销毁?与上面相同的约束可以吗?怎么样is_trivially_destructible?这个想法是破坏尚未构造的元素,反之亦然破坏构造的元素)应该没有害处。

  3. 除了给用户带来更多责任的明显风险之外,这种尝试是否存在重大缺陷?

重点是,当用户确实需要此类功能以提高性能时,较低级别的解决方案std::get_temporary_buffer(如手动分配(例如 with operator new()))在泄漏方面可能更具风险。我知道,std::vector::emplace_back()但这真的不是一回事。

4

1 回答 1

2

要回答问题:

  1. 没有限制T:如果它适用于标准容器,它适用于您的容器。
  2. 破坏是有条件的,你可以静态禁用它 if std::is_trivially_destructible<T>,否则你必须跟踪构造的元素并且只删除那些实际构造的元素。
  3. 我认为您的想法没有重大缺陷,但请确保它是值得的:分析您的用例并检查您是否确实花费了大量时间来初始化元素。

我假设您将容器实现为 size 的连续内存块size() * sizeof(T)。此外,如果必须调用元素的析构函数,即!std::is_trivially_destructible<T>,您必须启用额外的存储,例如用于标记要销毁的元素的 a std::vector<bool>of size()elements 。

基本上,如果T可以简单地破坏,您只需在用户询问时进行初始化,而不必费心破坏任何东西。否则,事情会有些棘手,您需要跟踪构建了哪些元素以及未初始化的元素,以便只销毁需要的元素。

  • 扩大规模或创建容器
    1. 如果!std::is_trivially_destructible<T>相应地调整大小标志存储
    2. 内存分配
    3. 根据用户的要求进行可选初始化:
      • no_init=> if !std::is_trivially_destructible<T>,将元素标记为未初始化。否则什么都不做。
      • (Args...)=> 如果std::is_constructible<T, class... Args>为每个元素调用该构造函数。如果!std::is_trivially_destructible<T>,则将元素标记为已构造。
  • 缩小尺寸或销毁容器
    1. 可选销毁:
      • 如果std::is_trivially_destructible<T>什么都不做
      • else 对于每个元素,如果它被标记为已构造,则调用其析构函数
    2. 内存释放
    3. 如果!std::is_trivially_destructible<T>相应地调整大小标志存储

从性能的角度来看,如果T是微不足道的破坏,事情就很棒了。如果它有一个析构函数,事情就更加复杂了:你获得了一些构造函数/析构函数调用,但是你需要维护额外的标志存储——最终这取决于你的构造函数/析构函数是否足够复杂。

也像评论中的一些建议一样,您可以使用基于的关联数组std::unordered_map、添加size_t vector_size字段、实现resize和覆盖size。这样,甚至不会存储未初始化的元素。另一方面,索引会更慢。

于 2014-02-27T11:07:10.470 回答