为什么 std::vector 在调整大小时不使用 memcpy 或 realloc

Why std::vector doesn't use memcpy or realloc when resizing

提问人:simd 提问时间:9/8/2023 更新时间:9/8/2023 访问量:123

问:

我了解到它会在调整大小时显式调用每个元素的复制构造函数,如果我理解正确的话,这意味着它不使用或在引擎盖下,这是我的第一个假设。我的理解正确吗?如果是这样,为什么他们需要以这种方式实现,而不是更好的是,在引擎盖下调整动态阵列大小的最快选择?std::vectormemcpyreallocstd::vectormemcpyrealloc

C++ 标准 向量

评论

1赞 wohlstad 9/8/2023
这当然取决于实现,但是您如何知道您的编译器/库没有对 POD 类型(在引擎盖下)应用此类优化?
3赞 Botje 9/8/2023
愚蠢的反例:.如果使用,则指针将失效。这种“远距离行动”是完全出乎意料的。struct Foo { Foo() : ptr(&x) {}; int x; int *ptr; }memcpyptrpush_backvector<Foo>
2赞 Alan Birtles 9/8/2023
没有什么可以阻止 using 的实现,或者如果他们选择这样做,并且元素的类型允许这样做std::vectormemcpyrealloc
1赞 Botje 9/8/2023
至于:这根本不总是可能的。内存分配器喜欢将分配打包在一起,以减少内存浪费。当然,您可以在内存分配之间设置千兆字节大小的孔,以最大限度地提高工作机会,但内存访问性能会下降。reallocrealloc
3赞 Peter 9/8/2023
该标准不要求实现(或者更准确地说,它的分配器)才能使用 和 ,但也不禁止这种使用。该决定将由标准库的实现者(例如,每个工具链附带的)做出。实际上,显式管理资源的元素类型通常不能很好地与 和 配合使用。对于其他类型的选择将归结为工程权衡 - 这不一定(与您的主张相反)是 or 的灌篮高手。std::vectormemcpy()realloc()memcpy()realloc()memcpy()realloc()

答:

3赞 Pete Becker 9/8/2023 #1

为某个类型使用显式复制构造函数的根本原因是,复制位不适用于该类型。因此,C++ 容器操作是在复制构造方面指定的。这并不意味着禁止复制 -able 类型的位;符合要求的程序无法检测值是通过赋值还是通过赋值复制的。事实上,如果你研究一下 std::copy 的实现,它们通常会用于内置类型。memcpyintmemcpymemcpy

评论

0赞 simd 9/8/2023
我的印象(现在仍然是)就如何完成内部复制而言,调整 std::vector 的大小是更轻松的操作。在我看来,在大多数情况下,即使是非平凡的可复制类型,也完全可以用于调整大小。但 Botje 给出的反例证明,至少在某些情况下它是不安全的。虽然除了自我引用之外,我想不出任何其他例子。memcpystruct Foo { Foo() : ptr(&x) {}; int x; int *ptr; }
1赞 Pete Becker 9/8/2023
@simd -- 从语言律师的角度来看,在符合要求的程序中可以检测到跳过复制构造函数(复制构造函数可能会写出一些调试信息),因此“as-if”规则不允许跳过它。为了允许它,标准必须严格定义允许跳过复制构造函数的条件,这充其量是一项不平凡的任务。但要振作起来:如果类型具有移动构造函数,则重新分配将使用移动构造函数。
1赞 Jesper Juhl 9/8/2023
@PeteBecker “如果具有移动构造函数的类型,则重新分配将使用 move 构造函数” - 仅当声明它不会引发异常 () (IIRC) 时。noexcept
0赞 simd 9/8/2023
是的,我很惊讶 std::vector 确实在一个完全微不足道的自定义类型上调用了我的复制构造函数(除了 std::cout 登录 copy-contructor 来检查它),但现在我意识到自定义复制构造器确实使它成为必要,可能仅凭它的存在......
2赞 François Andrieux 9/8/2023
@simd 根据定义,如果您的类型具有非平凡的复制构造函数,则它不是平凡的。如果登录复制构造函数,则它不是一个简单的复制构造函数。
1赞 Red.Wave 9/8/2023 #2

The official declaration of is:std::vector

template< typename T,
          typename Alloc = std::allocator<T> >
class std::vector;

The allocator parameter - like any other STL container - has a default instance of . This 2nd argument of the container controls the life-time (alloc/dealloc, construct/destroy) of the elements. The default allocator just may or may not use as the implementation of hint version of its member function. Copying data in C++ involves a lot of consideration. In general it is bad practice to use explicitly for this purpose. If the object to be copied - or any none-static data members or base base classes - provides user-defined copy constructor() and/or copy assignment operator(), then bitwise copy performed by leads to wrong results, possibility involving UB and all sort bugs. If on the other hand the type is trivially copyable, compiler is free to perform all sort of optimizations, including for copying on the type. The keeps one data member of its allocator type, in case the allocator is not an empty type( the default type is empty and uses empty allocator optimization). So if special treatment of memory is needed, user can provide his own version of allocator (which may hold state variables per instance) to STL class:std::allocatorstd::reallocallocatestd::memcpyT::T(T const&)T& T::operator(T&)memcpymemcpystd::vector

struct my_T_alloc;//provide the definitions
struct my_T;//define the type
std::vector<my_T, my_T_alloc> m_T_vec;

The custome allocator should provide the corresponding memeber functions of std::allocator_traits.