构建矢量以允许非初始化的存储

building a vector to allow uninitialized storage

本文关键字:初始化 存储 许非 构建      更新时间:2023-10-16

假设我想构建一个矢量容器,与std :: vector不同,该容器允许非初始化的存储。容器的使用情况,例如vec <T>,大致是这样的:

  • 用户明确指出,向量应分配n个非初始化元素:

    vec <T> a(N, no_init);

  • 在某个时候知道数据时,用户使用参数args...n处明确初始化元素:

    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或手动分配)(例如使用operator new())可能会更风险。我知道std::vector::emplace_back(),但这确实不是同一件事。

回答问题:

  1. T没有限制:如果它适用于标准容器,则适用于您的。
  2. 破坏是有条件的,如果std::is_trivially_destructible<T>,您可以在静态上禁用它,否则您必须跟踪构造的元素并仅删除实际构造的元素。
  3. 我没有看到您的想法中的主要缺陷,但是请确保值得:个人介绍您的用例并检查您确实花了很多时间初始化元素。

我假设您将容器作为大小size() * sizeof(T)的连续内存的块实现。另外,如果必须调用该元素的驱动器,即!std::is_trivially_destructible<T>,则必须启用额外的存储,例如size()std::vector<bool>元素用于标记破坏的元素。

基本上,如果T在易于破坏的情况下,您只需在用户询问并且不要打扰销毁任何东西时发起。否则,事情有些棘手,您需要跟踪构建哪个元素,哪些元素是不可分化的,因此您只会破坏所需的内容。

  • 上的大小或容器创建
    1. 如果!std::is_trivially_destructible<T>相应地调整标志大小
    2. 内存分配
    3. 可选的初始化,具体取决于用户的要求:
      • no_init =>如果!std::is_trivially_destructible<T>,将标志元素称为非判决。否则什么也不做。
      • (Args...) =>如果std::is_constructible<T, class... Args>为每个元素调用该构造函数。如果!std::is_trivially_destructible<T>,则构造的标志元素。
  • 缩减大小或容器破坏
    1. 可选破坏:
      • 如果std::is_trivially_destructible<T>无所事事
      • 对于每个元素,如果将其标记为构造,则称其为破坏者
    2. 内存DealLocation
    3. 如果!std::is_trivially_destructible<T>相应地调整标志大小

从性能的角度来看,如果T在巨大的破坏性上,情况就很棒。如果它具有破坏者,则事情会更加约束:您会获得一些构造函数/击路仪的调用,但是您需要维护其他标志存储 - 最后,是否取决于您的构造函数/驱动器是否足够复杂。

>

也像注释中建议的一样,您可以只使用基于std::unordered_map的关联数组,添加size_t vector_size字段,实现resize和Override size。这样,甚至不会存储非直接的元素。另一方面,索引较慢。