std::vector *必须*在增加容量时移动对象吗?或者,分配器可以"reallocate"吗?



另一个问题激发了我以下的想法:

std::vector<T> 是否有在增加容量时移动所有元素?

据我所知,标准行为是底层分配器请求新大小的整个块,然后移动所有旧元素,然后销毁旧元素,然后释放旧内存。

此行为似乎是给定标准分配器接口的唯一可能的正确解决方案。但是我想知道,修改分配器以提供一个reallocate(std::size_t)函数是否有意义,该函数将返回一个pair<pointer, bool>并可以映射到底层的realloc() ?这样做的好处是,如果操作系统实际上可以扩展分配的内存,那么根本不需要移动。布尔值表示内存是否移动。

(std::realloc())可能不是最好的选择,因为如果我们不能扩展,我们不需要复制数据。所以实际上我们更想要extend_or_malloc_new()这样的东西。编辑:也许基于is_pod特征的专门化将允许我们使用实际的realloc,包括它的位拷贝。只是不一般。)

这似乎是一个错失的机会。最坏的情况是,您可以始终将reallocate(size_t n)实现为return make_pair(allocate(n), true);,这样就不会有任何惩罚。

是否有什么问题使得该特性不适合或不受欢迎的c++ ?

也许唯一可以利用这一点的容器是std::vector,但话说回来,这是一个相当有用的容器。


更新:一个小例子来澄清。当前resize():

pointer p = alloc.allocate(new_size);
for (size_t i = 0; i != old_size; ++i)
{
  alloc.construct(p + i, T(std::move(buf[i])))
  alloc.destroy(buf[i]);
}
for (size_t i = old_size; i < new_size; ++i)
{
  alloc.construct(p + i, T());
}
alloc.deallocate(buf);
buf = p;
新实现:

pair<pointer, bool> pp = alloc.reallocate(buf, new_size);
if (pp.second) { /* as before */ }
else           { /* only construct new elements */ }

std::vector<T>的容量用完时,它必须分配一个新的块。你正确地叙述了原因。

在我看来,增加分配器接口是有意义的。我们中的两个试图为c++ 11获得支持,但我们无法获得支持:[1][2]

我开始确信,为了使这个工作,将需要一个额外的c级API。我也没能获得支持:[3]

在大多数情况下,realloc不会扩展内存,而是分配一个单独的块并移动内容。在最初定义c++时就考虑到了这一点,并决定当前的接口在一般情况下更简单,效率也不会低。

在现实生活中,实际上很少有realloc能够生长的情况。在malloc具有不同池大小的任何实现中,新大小(请记住vector的大小必须呈几何增长)都可能落在不同的池中。即使在没有从任何内存池中分配的大块的情况下,它也只能在较大大小的虚拟地址空闲时才能够增长。 请注意,虽然realloc有时可以在不移动的情况下增长内存,但是当realloc完成时,它可能已经移动了(按位移动)内存,并且二进制移动将导致所有非pod类型的未定义行为。我不知道任何分配器实现(POSIX, *NIX, Windows),您可以询问系统是否能够增长,但如果需要移动,这将失败。

是的,您是对的,标准分配器接口没有为memcpy'able类型提供优化。

可以使用boost类型特征库来确定类型是否可以memcpy(不确定它们是否提供开箱即用,或者必须基于boost类型特征库构建复合类型标识符)。

无论如何,要利用realloc(),可能需要创建一个新的容器类型,它可以显式地利用这种优化。对于当前的标准分配器接口,这似乎是不可能的。

相关内容

最新更新