中的类型擦除分配器

中的类型擦除分配器

本文介绍了现代C ++中的类型擦除分配器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

经典的STL容器,例如 std :: vector std :: map 作为模板参数。这意味着 std :: vector< T,std :: allocator< T>> std :: vector< T,MyAllocator& code>例如被认为是完全独立的类型。



一些较新的分配器感知类如 std :: shared_ptr std :: tuple 使用type-erasure隐藏有关分配器的信息,因此它不构成类型签名的一部分。但是, std :: unordered_map (与 shared_ptr 类似的复古)维护了一个额外的默认的模板参数。



问题:


  1. c $ c> std :: vector< T,std :: allocator< T> std :: vector< T,MyAllocator> 作为不同的类型被认为是可取的,还是它只是类型擦除的副作用,而不是在编写STL时的公知技术?



  2. 应该类型擦除的分配器总是首选新容器吗?

  3. 另一方面,使用type-erasure隐藏关于分配器的信息,c $ c> std :: shared_ptr
    std :: tuple 因此它不构成类型签名的一部分。


    std :: tuple 根本不使用类型擦除。元组可以用一个分配器构造,但它只是(有条件地)将它传递给它的元素,它不会存储在任何地方,因为元组从不分配任何内存,因此不需要分配器。



    std :: shared_ptr 会分配内存,所以它可以使用分配器,它将存储直到控制块需要被释放。因为控制块已经对于用户是不可见的并且存储在堆上,所以与该控制块相关联的分配器对于用户也是不可见的。



    因此与 shared_ptr 不太相关,因为它对不适用于容器的分配器有完全不同的用途。

    STL中分配器的原始动机是封装关于内存模型的细节,和far指针的。这就是为什么allocator定义了容器在内部使用的指针成员。使用near指针的向量不能使用far指针将其元素的地址与另一个容器的地址混合。



    因此,对于原始使用,具有不同类型的




    • 所有函数调用都必须是虚函数(或某种其他形式的间接调用,例如通过函数指针),并且更难以内联。这不是 shared_ptr 的问题,它只是在擦除分配器类型之前分配一些内存,然后再次使用它来释放内存,但通用容器可能使成千上万个


    • 类型删除的分配器很难从容器中检索,使得创建容器的副本变得复杂。 (它应该使用分配器的副本?你如何复制你看不到的东西?)对于 shared_ptr 类型,这不是一个问题,因为复制<$


    • 对象通常需要是size by sizeof(void *)以存储类型已擦除的分配器。即使allocator是一个空的无状态类型,例如 std :: allocator< T> ,这个额外的指针也不能被优化。根据类型,可以意味着大小增加50%或甚至100%,与能够利用空基类优化来存储空分配器相比。这不是 shared_ptr 的问题,因为除非创建或销毁控制块,否则不需要分配器,因此不需要访问<$


    • 由于类型已擦除的分配器必须满足特定的分配要求,因此必须使用c $ c> shared_ptr 抽象接口,它必须在其分配 deallocate 成员中使用原始指针。这意味着您不能使用自定义指针类型,例如




















    我会说不。如果分配器是类型的一部分,则可以在常见情况下优化它,同时仍允许容器的用户选择在内部使用类型擦除的多态分配器,例如


    The "classic" STL containers such as std::vector and std::map take their allocator types as a template argument. This means that std::vector<T, std::allocator<T>> and std::vector<T, MyAllocator> for example are considered completely separate types.

    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. However, std::unordered_map (which is of a similar vintage to shared_ptr) maintains the classic approach of taking an extra defaulted template parameter.

    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?

    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?

    解决方案

    std::tuple doesn't use type-erasure at all. 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. 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.

    The original motivation for allocators in the STL was to encapsulate details about the memory model, specifically "near" and "far" pointers of segmented memory. This is why the allocator defines a pointer member which the container uses internally. 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.

    • All function calls have to be virtual (or some other form of indirect call e.g. 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.

    • 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.

    • The object generally needs to be larger by sizeof(void*) to store the type-erased allocator. That extra pointer can't be optimized away, even if the allocator is an empty, stateless type such as 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. 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.

    • Because a type-erased allocator has to meet an specific abstract interface it has to use raw pointers in its allocate and deallocate members. This mean you can't use a custom pointer type, e.g. a pointer that stores a relative offset to a base address, which is useful for shared-memory allocators as used in Boost.Interprocess.

    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

    这篇关于现代C ++中的类型擦除分配器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-03 05:17