未使用的 STL 容器是否分配内存?

Does an unused STL container allocate memory?

给定代码:

class Foo {
  std::vector<int> items;
  std::map<int, int> dictionary;
};
  1. 如果上面的向量或映射中没有添加任何内容,是否仍会分配一块缓冲内存? (换句话说,缓冲区分配是否总是在容器创建期间发生,还是可以推迟到调用 push_back 之类的函数?)

  2. 是否有处理初始 STL 容器缓冲区分配时间的标准,或者该行为是否允许在 STL 容器和编译器之间有所不同?

注意:这个问题不是关于这些容器会添加到 class Foo 的大小的额外字节。

(此问题的一个相关子集,重点是分配大小 Initial capacity of vector in C++。)

标准对此没有任何说明,但我专门研究的实现将为 std::vector 进行一些预分配,并且不会为 std::map 进行任何预分配。

当我有一个巨大的容器时,这实际上曾经给我很大的打击,其中的元素很小 - 不超过 10 个元素,大多数条目都有 0 大小的向量 - 向量在里面。此实现中的默认向量容量为 32,而 '32 * sizeof(vector_element) * number_of_elements' 恰好非常大。

  1. If nothing is ever added to the above vector or map, will either still allocate a block of memory for potential entries? (In other words, does entry allocation always happen during container creation or can it be deferred until calls to functions like push_back?)

这可能发生,是的。它实际上是容器实现的一个细节,并没有在标准中指定。

  1. Is there a standard for handling the timing of initial STL container allocation or is that behavior allowed to vary between STL containers and compilers?

您可以使用例如延迟创建a std::unique_ptr 用于成员,并通过调用 getter 函数创建它们。

C++ Reference 对于 C++17,默认构造函数是 noexcept 当且仅当分配器构造是 noexcept。所以这取决于使用的分配器。在 VS 2015 中,标准构造函数是 noexcept.

说明:这意味着如果分配器不是 noexcept,则不会分配任何内存块。

关于你的第二个问题:同样的参考,它是 O(1)。

如前所述,这不是很好定义。但是,您可以测试一下。

例如gcc/linux。制作一个简单的程序,在 gdb 中用 -O0 -g 和 运行 编译它。那么

break main
run
break malloc
cont

现在只需在每个 malloc 上 运行 一个 backtrace,您就会看到动态分配。使用我的 gcc 5.3.0,两个空容器都不分配堆内存,这是在第一个 push_back / operator[].

上完成的

当然,如果不是 gdb / malloc.

,你应该使用你喜欢的调试器并中断你的分配器底层函数

现在,如果您考虑这两种情况。在这种情况下预分配内存有意义吗?

std::vector<int> foo;
foo.push_back(13);

好吧,从技术上讲,您可以保存 nullptr 的检查,但是使用将向量实现为 3 个指针的通常方法,不需要额外的检查。

但考虑一下

std::vector<int> foo;
foo.reserve(100);

在这种情况下,预分配会损害性能。

我找不到像 map 这样的树结构的预分配参数。

请记住,这是一个非常具体的优化。仅在有充分理由(基准!)的情况下为此进行优化。

注意:您可能想阅读小字符串优化,这是一种非常常见的技术,既相关又不同。

值得注意的是,Microsoft 的 STL 实现目前 确实std::mapstd::set 的默认构造函数中分配。 Godbolt example - 注意汇编输出第 8 行对 operator new 的调用。

是的,这绝对会影响性能。我只是在分析了一些神秘而缓慢的代码后才意识到这一点,结果发现带有很少使用的 map 成员的 class 的默认构造函数支配了 运行-时间有问题的循环。

如果您和我一样,对这个实施决定并不完全满意,我会指出 Boost.Container's counterparts do guarantee 零分配默认构造。