简体   繁体   English

现代C ++中类型擦除的分配器

[英]Type-erased allocators in modern C++

The "classic" STL containers such as std::vector and std::map take their allocator types as a template argument. “经典” STL容器(例如std::vectorstd::map将其分配器类型作为模板参数。 This means that std::vector<T, std::allocator<T>> and std::vector<T, MyAllocator> for example are considered completely separate types. 这意味着例如std::vector<T, std::allocator<T>>std::vector<T, MyAllocator>被视为完全独立的类型。

Some newer allocator-aware classes like std::shared_ptr and std::tuple on the other hand use type-erasure to "hide" information about the allocator, so it does not form part of the type signature. 另一方面,一些较新的可识别分配器的类(例如std::shared_ptrstd::tuple使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分。 However, std::unordered_map (which is of a similar vintage to shared_ptr ) maintains the classic approach of taking an extra defaulted template parameter. 但是, std::unordered_map (与shared_ptr具有类似的年份)保持了采用额外默认模板参数的经典方法。

Questions: 问题:

  1. Is treating std::vector<T, std::allocator<T>> and std::vector<T, MyAllocator> as distinct types considered desirable, or is it just a side effect of type-erasure not being a well-known technique at the time the STL was written? 正在将std::vector<T, std::allocator<T>>std::vector<T, MyAllocator>视为被认为是理想的不同类型,或者仅仅是类型擦除的副作用不是众所周知的STL编写时的技术?

  2. What are the downsides (if any) of using type-erasure in this way? 以这种方式使用类型擦除的不利之处(如果有)?

  3. Should type-erased allocators always be preferred for new containers? 对于新容器,应始终首选使用类型擦除的分配器吗?

Some newer allocator-aware classes like std::shared_ptr and std::tuple on the other hand use type-erasure to "hide" information about the allocator, so it does not form part of the type signature. 另一方面,一些较新的可识别分配器的类(例如std::shared_ptrstd::tuple使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分。

std::tuple doesn't use type-erasure at all. std::tuple根本不使用类型擦除。 A tuple can be constructed with an allocator, but it just (conditionally) passes it to its elements, it doesn't store it anywhere, because a tuple never allocates any memory so has no need for an allocator. 可以使用分配器构造一个元组,但是它只是(有条件地)将其传递到其元素,它不会将其存储在任何地方,因为元组从不分配任何内存,因此不需要分配器。

std::shared_ptr does allocate memory, so it can use an allocator, which it will store until the control block needs to be deallocated. std::shared_ptr确实分配了内存,因此它可以使用分配器,它将一直存储直到需要释放控制块为止。 Since the control block is already invisible to users and stored on the heap, the allocator associated with that control block is also invisible to users. 由于控制块对于用户已经是不可见的并且存储在堆中,因此与该控制块关联的分配器对用户也是不可见的。

So the comparison to shared_ptr is not very relevant, because it has completely different uses for an allocator that don't apply to containers. 因此,与shared_ptr的比较不是很相关,因为它对不适用于容器的分配器具有完全不同的用途。

  1. Is treating std::vector<T, std::allocator<T>> and std::vector<T, MyAllocator> as distinct types considered desirable, or is it just a side effect of type-erasure not being a well-known technique at the time the STL was written? 正在将std::vector<T, std::allocator<T>>std::vector<T, MyAllocator>视为被认为是理想的不同类型,或者仅仅是类型擦除的副作用不是众所周知的STL编写时的技术?

The original motivation for allocators in the STL was to encapsulate details about the memory model, specifically "near" and "far" pointers of segmented memory . STL中分配器的最初动机是封装有关内存模型的详细信息,特别是分段内存的 “近”和“远”指针。 This is why the allocator defines a pointer member which the container uses internally. 这就是分配器定义容器内部使用的pointer成员的原因。 A vector using near pointers must not mix up addresses of its elements with those in another container using far pointers, for example. 例如,使用近指针的向量不得将其元素的地址与使用远指针的另一个容器中的地址混淆。

So for the original use, having distinct types was valuable, but that original use is irrelevant these days. 因此,对于原始用途而言,具有不同的类型非常有价值,但是如今这些原始用途已无关紧要。

  1. What are the downsides (if any) of using type-erasure in this way? 以这种方式使用类型擦除的不利之处(如果有)?
  • All function calls have to be virtual (or some other form of indirect call eg through a function pointer) and are much harder to inline. 所有函数调用都必须是虚拟的(或其他某种形式的间接调用,例如通过函数指针),并且内联起来要困难得多。 This isn't a problem for shared_ptr which just allocates some memory once before erasing the allocator type then uses it once more to free the memory, but general-purpose containers might make thousands of allocations. 对于shared_ptr这不是问题, shared_ptr只会在擦除分配器类型之前分配一次内存,然后再使用它释放内存,但是通用容器可能会进行数千次分配。

  • A type-erased allocator is much harder to retrieve from the container, making it complicated to create a copy of the container. 从容器中检索类型擦除的分配器要困难得多,这使得创建容器的副本变得很复杂。 (Should it use a copy of the allocator? How do you copy something you can't see?) This isn't a problem for types like shared_ptr because copying a shared_ptr just increases the reference-count, it doesn't allocate anything. (它应该使用分配器的副本吗?如何复制看不到的东西?)这对于诸如shared_ptr类的类型来说不是问题,因为复制shared_ptr只会增加引用计数,而不会分配任何内容。

  • The object generally needs to be larger by sizeof(void*) to store the type-erased allocator. 通常,该对象需要增大sizeof(void*)才能存储类型擦除的分配器。 That extra pointer can't be optimized away, even if the allocator is an empty, stateless type such as std::allocator<T> . 即使分配器是空的,无状态的类型,例如std::allocator<T> ,也无法优化掉多余的指针。 Depending on the type that could mean a 50% or even 100% increase in size compared to being able to exploit the Empty Base-class Optimization to store an empty allocator. 与能够利用Empty Base-class Optimization存储空分配器相比,取决于类型可能意味着大小增加50%甚至100%。 This isn't a problem for shared_ptr because the allocator isn't needed except when creating or destroying the control block, so it doesn't need to be accessible for the shared_ptr to use for other (de)allocations. 这对于shared_ptr来说不是问题,因为除了创建或销毁控制块时不需要分配器,因此shared_ptr不需要访问它即可用于其他(取消)分配。

  • Because a type-erased allocator has to meet an specific abstract interface it has to use raw pointers in its allocate and deallocate members. 由于类型擦除的分配器必须满足特定的抽象接口,因此必须在其allocatedeallocate成员中使用原始指针。 This mean you can't use a custom pointer type, eg a pointer that stores a relative offset to a base address, which is useful for shared-memory allocators as used in Boost.Interprocess. 这意味着您不能使用自定义pointer类型,例如,存储相对于基地址的相对偏移的指针,这对于Boost.Interprocess中使用的共享内存分配器很有用。

  1. Should type-erased allocators always be preferred for new containers? 对于新容器,应始终首选使用类型擦除的分配器吗?

I would say no. 我会说不。 If the allocator is part of the type you can optimize it away for the common cases, while still allowing users of the container to choose a polymorphic allocator that uses type erasure internally, such as the ones in the Library Fundamentals TS 如果分配器是类型的一部分,则可以针对常见情况对其进行优化,同时仍然允许容器的用户选择内部使用类型擦除的多态分配器,例如Library Fundamentals TS中的分配器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM