std::vector
和std::map
等“经典”STL容器将其分配器类型作为模板参数。这意味着std::vector<T, std::allocator<T>>
和std::vector<T, MyAllocator>
被视为完全独立的类型。
另一方面,一些较新的分配器感知类(如std::shared_ptr
和std::tuple
)使用类型擦除来“隐藏”有关分配器的信息,因此它不构成类型签名的一部分。但是,std::unordered_map
(与shared_ptr
类似)保留了采用额外默认模板参数的经典方法。
问题:
将std::vector<T, std::allocator<T>>
和std::vector<T, MyAllocator>
视为理想的不同类型,或者只是在STL编写时类型擦除的副作用不是众所周知的技术
以这种方式使用类型擦除有哪些缺点(如果有的话)?
类型擦除的分配器是否总是首选新容器?
答案 0 :(得分:5)
另一方面,一些较新的分配器感知类(如
std::shared_ptr
和std::tuple
)使用类型擦除来隐藏&#34;隐藏&#34;有关分配器的信息,因此它不构成类型签名的一部分。
std::tuple
根本不使用类型擦除。元组可以用分配器构造,但它只是(有条件地)将它传递给它的元素,它不会将它存储在任何地方,因为元组从不分配任何内存,因此不需要分配器。
std::shared_ptr
确实分配了内存,所以它可以使用一个分配器,它将存储直到需要释放控制块。由于控制块已经对用户不可见并存储在堆上,因此与该控制块关联的分配器对用户也是不可见的。
因此与shared_ptr
的比较并不十分相关,因为它对于不适用于容器的分配器具有完全不同的用途。
- 将
醇>std::vector<T, std::allocator<T>>
和std::vector<T, MyAllocator>
视为理想的不同类型,或者只是在编写STL时类型擦除的副作用不是众所周知的技术?
STL中分配器的最初动机是封装有关内存模型的详细信息,特别是&#34; near&#34;和&#34;远&#34;指针segmented memory。这就是分配器定义容器在内部使用的pointer
成员的原因。例如,使用近指针的向量不得将其元素的地址与使用远指针的另一个容器中的地址混淆。
因此,对于原始用途,具有不同类型是有价值的,但这些日子原始用途是无关紧要的。
- 以这种方式使用类型擦除有哪些缺点(如果有的话)?
醇>
所有函数调用都必须是虚拟的(或者通过函数指针进行其他形式的间接调用)并且更难以内联。这对于shared_ptr
来说不是问题,它只是在擦除分配器类型之前分配一些内存然后再次使用它来释放内存,但是通用容器可能会进行数千次分配。
从容器中检索类型擦除的分配器要困难得多,这使得创建容器副本变得复杂。 (它应该使用分配器的副本吗?如何复制您无法看到的内容?)对于像shared_ptr
这样的类型,这不是问题,因为复制shared_ptr
只会增加引用计数,它没有分配任何东西。
对象通常需要大sizeof(void*)
来存储类型擦除的分配器。即使分配器是空的无状态类型,例如std::allocator<T>
,也不能优化掉额外的指针。与能够利用Empty Base-class Optimization来存储空分配器相比,可能意味着大小增加50%甚至100%的类型。这对shared_ptr
来说不是问题,因为除了创建或销毁控制块之外,不需要分配器,因此shared_ptr
不需要访问它用于其他(de)分配。
因为类型擦除的分配器必须满足特定的抽象接口,所以它必须在其allocate
和deallocate
成员中使用原始指针。这意味着您无法使用自定义pointer
类型,例如一个指针,用于存储基址的相对偏移量,这对于Boost.Interprocess中使用的共享内存分配器非常有用。
- 类型擦除的分配器是否总是首选新容器?
醇>
我会说不。如果分配器是类型的一部分,您可以针对常见情况对其进行优化,同时仍然允许容器的用户选择在内部使用类型擦除的多态分配器,例如Library Fundamentals TS