另一个问题激发了我以下的想法:
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级API。我也没能获得支持:[3]
在大多数情况下,realloc
不会扩展内存,而是分配一个单独的块并移动内容。在最初定义c++时就考虑到了这一点,并决定当前的接口在一般情况下更简单,效率也不会低。
realloc
能够生长的情况。在malloc
具有不同池大小的任何实现中,新大小(请记住vector
的大小必须呈几何增长)都可能落在不同的池中。即使在没有从任何内存池中分配的大块的情况下,它也只能在较大大小的虚拟地址空闲时才能够增长。
请注意,虽然realloc
有时可以在不移动的情况下增长内存,但是当realloc
完成时,它可能已经移动了(按位移动)内存,并且二进制移动将导致所有非pod类型的未定义行为。我不知道任何分配器实现(POSIX, *NIX, Windows),您可以询问系统是否能够增长,但如果需要移动,这将失败。
是的,您是对的,标准分配器接口没有为memcpy'able类型提供优化。
可以使用boost类型特征库来确定类型是否可以memcpy(不确定它们是否提供开箱即用,或者必须基于boost类型特征库构建复合类型标识符)。
无论如何,要利用realloc()
,可能需要创建一个新的容器类型,它可以显式地利用这种优化。对于当前的标准分配器接口,这似乎是不可能的。