gpt4 book ai didi

c++ - 构建一个 vector 以允许未初始化的存储

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:10:26 25 4
gpt4 key购买 nike

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

  • 用户明确声明 vector 应该像这样分配 N 个未初始化的元素:

    vec <T> a(N, no_init);

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

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

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

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

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

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

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

所以我的问题是:

  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> ,您必须启用额外的存储,例如 std::vector<bool>size()元素用于标记要销毁的元素。

基本上,如果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>什么都不做
      • else 对于每个元素,如果它被标记为已构造,则调用它的析构函数
    2. 内存释放
    3. 如果!std::is_trivially_destructible<T>相应地调整标志存储的大小

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

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

关于c++ - 构建一个 vector 以允许未初始化的存储,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21585675/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com