C++ 在现代C++;

C++ 在现代C++;,c++,c++11,stl,type-erasure,allocator,C++,C++11,Stl,Type Erasure,Allocator,“经典”STL容器,如std::vector和std::map将其分配器类型作为模板参数。这意味着,例如,std::vector和std::vector被认为是完全独立的类型 另一方面,一些较新的分配器感知类,如std::shared_ptr和std::tuple使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分。但是,std::unordered_map(与shared_ptr)保持了采用额外默认模板参数的经典方法 问题: 将std::vector和std::vector视为

“经典”STL容器,如
std::vector
std::map
将其分配器类型作为模板参数。这意味着,例如,
std::vector
std::vector
被认为是完全独立的类型

另一方面,一些较新的分配器感知类,如
std::shared_ptr
std::tuple
使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分。但是,
std::unordered_map
(与
shared_ptr
)保持了采用额外默认模板参数的经典方法

问题:

  • std::vector
    std::vector
    视为不同的类型被认为是可取的,还是仅仅是在编写STL时类型擦除不是一种众所周知的技术的副作用

  • 以这种方式使用类型擦除有什么缺点(如果有的话)

  • 对于新容器,是否应始终首选类型擦除的分配器

  • 另一方面,一些较新的分配器感知类,如
    std::shared_ptr
    std::tuple
    使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分

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

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

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

  • std::vector
    std::vector
    视为不同的类型被认为是可取的,还是仅仅是在编写STL时类型擦除不是一种众所周知的技术的副作用
  • STL中分配器的最初动机是封装内存模型的细节,特别是内存的“近”和“远”指针。这就是分配器定义容器内部使用的
    指针
    成员的原因。例如,使用近指针的向量不能将其元素的地址与另一个使用远指针的容器中的地址混淆

    因此,对于最初的使用,拥有不同的类型是有价值的,但这种最初的使用现在已经不重要了

  • 以这种方式使用类型擦除有什么缺点(如果有的话)
    • 所有函数调用都必须是虚拟的(或其他形式的间接调用,例如通过函数指针),并且更难内联。这对于
      shared_ptr
      来说不是问题,它在删除分配器类型之前只分配一次内存,然后再次使用它释放内存,但通用容器可能会进行数千次分配

    • 类型擦除的分配器更难从容器中检索,这使得创建容器副本变得复杂。(它应该使用分配器的副本吗?如何复制看不到的内容?)这对于
      shared\u ptr
      之类的类型来说不是问题,因为复制
      shared\u ptr
      只会增加引用计数,它不会分配任何内容

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

    • 因为类型擦除分配器必须满足特定的抽象接口,所以它必须在其
      allocate
      deallocate
      成员中使用原始指针。这意味着您不能使用自定义的
      指针
      类型,例如存储到基址的相对偏移量的指针,这对于Boost.Interprocess中使用的共享内存分配器很有用

  • 对于新容器,是否应始终首选类型擦除的分配器

  • 我会说不。如果分配器是类型的一部分,您可以针对常见情况对其进行优化,同时仍然允许容器用户选择内部使用类型擦除的多态分配器,例如

    侧注:泛型类型擦除分配器实际上是在中提出的。@user657267:我不知道,感谢您提供的信息,我将阅读:-)使用类型擦除分配器的缺点是(通常)性能。当编译器可以看到所有内容时,它可以内联例程,等等。当你有一个类型被删除的东西时,它所能做的就是调用这些例程。马歇尔的进一步评论。。。当您有理由关心时,通常可以编写一个分配器类型,该类型在内部使用类型擦除来分派给各种具体分配器,从而允许您使用相同的外部分配器并避免不同的容器实例化。两全其美。