【问题标题】:Why does std::vector initializes its content?为什么 std::vector 初始化它的内容?
【发布时间】:2021-10-26 00:33:02
【问题描述】:

假设我想创建一个vector 并在其上调用iota

std::vector<int> v(1000);
std::iota(begin(v),end(v),0);

向量实际上在构造函数中初始化为 0。问题与以下内容相同:

std::vector<int> v;
v.resize(1000);

我可以使用reserve,但我无法拨打iota。而且我没有看到不进行零初始化的优雅方法(也许使用自定义分配器......?)。

为什么vector 是这样设计的?我知道 Stepanov 和合作者非常仔细地制作了 STL,并且几乎没有缺陷。但对我来说,它似乎与“你不为你不使用的东西付费”相冲突。这种设计的动机是什么?

是否有共识认为不应该那样做? (例如,用于size() 的无符号size_t 就是这种情况,人们似乎同意它应该与difference_type 是同一类型,因为我们经常比较索引和size())。

更多信息

性能

关于生成的代码的一些比较。编译器会优化 0-init 吗?使用vector 时很难查看汇编程序,因为它做了很多事情,所以我尝试了这段代码:

int main() {
    int* v = new int[1000]; // clearer for reading assembly
    fill(v,v+1000,0);
    iota(v,v+1000,0);
    return v[999]; // do not optimize away!
}

Result here。然后with std::fill removed

够简单了吧?嗯,汇编和gcc或者clang-O3一样,和std::fill比较长,一般说明是真的发出指令了。

所以除非我误解了,否则它确实很重要。

历史

原始STL的代码可以在here找到。在版本 2 中,向量的内容已经被默认初始化。委员会似乎验证了已经存在的内容。

std::array的比较

评论中有些人认为你绝对必须初始化向量的内容。但证明这确实是一个设计决定的证据是 std::array 相反,默认初始化其内存。所以也许更容易或更有意义地实现vectorarray,但对于两者来说,反过来也是可能的。

【问题讨论】:

  • 我删除了 stl 标记,因为它看起来像您使用的是 C++ 标准库。请参阅 What's the difference between “STL” and “C++ Standard Library”?stl 标记说明。
  • 谢谢,但我再说一遍,因为 AFAIK 的设计选择是在标准化之前由 STL 做出的,所以我认为拥有历史视角也是有意义的
  • 这不是我的问题。我问的是为什么,而不是如何。其他问题没有说明为什么要这样做。是否可以重新开放?
  • 希望有一天我们会看到 Stroustrup(可能还有其他一些人)的The Continuing Evolution of C++,它解释了这些决定背后的理性,但它可能源于只是 malloced 的内存块中的基本类型或 POD 类型是否真的是真实的,而不是“总是有效”的 UB。
  • 我认为缺少的是std::iota_n,因为这样您就可以传递一个指向您保留的向量的std::back_inserter

标签: c++ stl std stdvector


【解决方案1】:

实际上,您可以避免这种情况。从cppreference 引用例如resize

附加默认插入的元素

讨论C++ named requirements: DefaultInsertable 的页面说(强调我的):

如果不希望值初始化,例如,如果对象是非类类型并且不需要清零,则可以通过提供自定义 Allocator::construct 来避免这种情况 p>

最后,link back to Stack Overflow (!) 显示了如何做到这一点(我没有详细分析此代码)。

当然,对于非 POD 类型,您确实希望新元素是默认构造的。否则,访问它们(甚至试图分配给它们)会导致 UB。

【讨论】:

  • 我没有仔细看,但似乎vector类型应该是e.g. std::vector&lt;int,default_init_alloc&gt; 对吗?如果是这样,它是重量级的(不能再使用法线向量了!)但至少它解决了问题。
  • 是的,您将自定义分配器作为最后一个模板参数传递给std::vector。不过,我不会将其描述为“重量级”——我认为它不会花费太多。
  • 是的,不是重量级的,因为代码成本更高,但因为那时我使用另一种向量类型,这意味着我可能必须调整所有未在分配器上模板化的接口
【解决方案2】:

std::vector 的各种属性是针对特定用例设计的,并满足某些要求。其中一些要求是:

  1. 连续内存
  2. push_back 的恒定摊销时间复杂度
  3. 随机访问迭代器

也许还有其他一些人。

C++ 库中还有其他容器具有其他属性并针对其他用例进行了优化,例如std::liststd::queue。这就是为什么会有不同的容器:它们针对不同的情况进行了优化。 std::vector 需要可复制/可移动类型。其他容器没有。如果您有不可复制/可移动的类型,则根本不能使用std::vector

没有声称std::vector 或任何其他容器将在所有可能的情况下提供最佳结果。 C++ 库中没有万事通的容器比其他所有东西都做得更好。这就是为什么存在不同容器的原因,正确使用它们的一个关键因素是了解每个容器的用途。

您已经描述了std::vector 不会产生最佳结果的几种情况。这并不表示std::vector 的设计存在缺陷,这根本不是它的意图。

为什么要这样设计vector?

它的设计目的是在特定用例中提供最佳结果。

这样设计的动机是什么?

这样设计的动机主要是为了让std::vector 为:

  1. 连续内存
  2. push_back 的恒定摊销时间复杂度
  3. 随机访问迭代器

是否有一个共识,回顾过去,它不应该是 就这样吗?

不,实际上恰恰相反。目前的共识是,std::vector 使用现代 CPU 硬件(在正确使用时)提供了令人惊讶的好结果,现代 CPU 硬件在连续内存访问方面表现出色,即使与其他容器相比,乍一看,这些容器可以更好地匹配某些用例。

【讨论】:

  • OK 可以说我想要一个与std::vector 完全相同的容器,只是我不希望它初始化数据。我应该使用什么?
  • 对我来说,问题是如果反过来,即向量不会默认构造元素,而我想要它,我总是可以调用std::fill。而现在这样,我别无选择!
  • 抱歉,C++ 不能这样工作。在 C++ 中,创建对象时必须构造它。这是 C++ 的基础。如果一个容器包含something,那么无论是什么东西都必须被构造。否则容器中不能有任何东西。您要解决的真正问题是什么?不,不是关于带有“未初始化”对象的容器的问题,而是您认为解决方案是带有未初始化对象的容器的问题,所以这就是您要问的问题。
  • 哦,是的,它确实是这样工作的!对于std::array,它以这种方式工作,它完全有可能与std::vector 一起工作。再一次,int 没有默认初始化,为什么所有非内置都必须是?
  • 如果您尝试将std::array 与具有默认构造函数的类一起使用,您应该能够看到数组中的每个值都是默认构造的。
【解决方案3】:

不是真正回答您的问题,而是回答您的问题(未经测试的代码):

vector<int> v;
int val = 0;
v.reserve(1000);
std::generate_n(std::back_inserter(v), 1000, [&val]() { return val++; });

【讨论】:

    猜你喜欢
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 2017-12-21
    • 2013-06-06
    • 2020-10-28
    • 2013-07-13
    • 2012-06-09
    • 1970-01-01
    相关资源
    最近更新 更多