C++ STL——allocator
C++ 标准模板库提供了一个非常重要的内存管理机制——allocator。它对内存管理接口进行了封装,实现了对象内存分配与构造分离、对象析构与内存释放分离。 标准库中通过模板参数为容器提供内存管理策略,allocator 就是一种具体的策略,我们可以实现基于共享内存、内存池的 自定义 allocator 把它们应用于容器。本文章将以 MSVC 的 allocator 框架为参考,讲述 allocator 的核心工作原理。
1. allocator_traits
allocator_traits,它是 C++11 开始引入的“萃取器”,主要用于“萃取” allocator 的类型,并且 C++11 之后 rebind_alloc 也被移入到 allocator_traits 中。MSVC 中 allocator_traits 的实现方式是:为std::allocator 的 traits 继承 _Default_allocator_traits,如下代码片段:
struct _Default_allocator_traits
{ // [0.0] “萃取” allocator 类型、元素类型using allocator_type = _Alloc;using value_type = typename _Alloc::value_type;// [0.1] rebind_alloc template<class _Other>using rebind_alloc = std::allocator<_Other>;
};template<class _Alloc>
struct allocator_traits : _Default_allocator_traits<_Alloc> {};
2. rebind_alloc
rebind_alloc, 因为list等容器的元素类型与结点的类型并不一致,所以要单独为结点类型对象内存分配、构造等目的提供一个allocator,其命名含义不必细究。
// 使用 alias template 定义了泛型的别名
template<class _Alloc, class _Value_type>
using _Rebind_alloc_t = typename allocator_traits<_Alloc>::template rebind_alloc<_Value_type>;
3. 使用 allocator
allocator 主要作为容器的模板参数使用,如下 list 模板的第二个模板参数。
// [3.0] 结点类型
template <class _Value_type>
struct _List_node { // list nodeusing value_type = _Value_type;void* _Next; // successor node, or first element if headvoid* _Prev; // predecessor node, or last element if head_Value_type _Myval; // the stored value, unused if head
};template<class _Ty, class _Alloc = std::allocator<_Ty>>
class list {
public:// [3.0] list node template instanceusing _Node = _List_node<_Ty>;// [3.1] 定义了结点类型对应的 allocatorusing _Alnode = _Rebind_alloc_t<_Alloc, _Node>;// [3.4] list<int>, _Alnode <==> std::allocator<_List_node<int>>;_Compressed_pair<_Alnode, _Scary_val> _Mypair;public:// [3.2] 向容器(假设是list<int>类型)添加元素void push_back(const _Ty& _Val) {_Emplace(_Mypair._Myval2._Myhead, _Val);}template <class... _Valty>_Nodeptr _Emplace(const _Nodeptr _Where, _Valty&&... _Val) {// list<int>, _Alnode <==> std::allocator<_List_node<int>>// _Getal() <==> std::allocator<_List_node<int>>// [3.3] 这里没有使用直接使用allocator接口,而是借助一个类对象_List_node_emplace_op2<_Alnode> _Op{_Getal(), forward<_Valty>(_Val)...};}// [3.4] 数据成员中有list的结点对象分配器“实例”,该函数返回它的引用// list<int>, _Alnode <==> std::allocator<_List_node<int>>;_Alnode& _Getal() noexcept {return _Mypair._Get_first();}
};
4. 容器结点分配器类
该类对象作为容器类数据成员,提供 allocator 相关能力
// store a pair of values, deriving from empty first
template <class _Ty1, class _Ty2, bool = is_empty_v<_Ty1> && !is_final_v<_Ty1>>
class _Compressed_pair final : private _Ty1 {
public:_Ty2 _Myval2;// list<int>, _Ty1 <==> std::allocator<_List_node<int>>constexpr _Ty1& _Get_first() noexcept {return *this;}
};
5.构造行为封装
上文提到向容器中添加元素(在某个位置构造)操作由该类进行封装
// [5.0] 父类
template <class _Alloc>
struct _Alloc_construct_ptr {_Alloc& _Al;explicit _Alloc_construct_ptr(_Alloc& _Al_) : _Al(_Al_) {}
};// [5.0] 子类
template <class _Alnode>
struct _List_node_emplace_op2 : _Alloc_construct_ptr<_Alnode> {// list<int>, _Alnode <==> std::allocator<_List_node<int>>,// allocator_traits<_Alnode> <==> allocator_traits<std::allocator<_List_node<int>>>// allocator_traits::allocator_type <==> std::allocator<_List_node<int>>// [5.1] 结点 allocator 的“萃取器”using _Alnode_traits = allocator_traits<_Alnode>;using pointer = typename _Alnode_traits::pointer;// [5.1] 注意构造该类对象时引用了容器的 allocatortemplate <class... _Valtys>explicit _List_node_emplace_op2(_Alnode& _Al_, _Valtys&&... _Vals) : _Alloc_construct_ptr<_Alnode>(_Al_) {this->_Allocate();// allocator_traits<std::allocator<_List_node<int>>>::construct(// this->_Al, addressof(this->_Ptr->_Myval),// std::forward<_Valtys>(_Vals)...);// [5.2] 结点 allocator 的“萃取器”的静态成员函数完成元素构造_Alnode_traits::construct(this->_Al, addressof(this->_Ptr->_Myval),std::forward<_Valtys>(_Vals)...);}
};
6. 封装 allocate、deallocate、construct、destroy
这个类型看着比较面熟,没错前面只列出它的类型别名,现在为它添加核心函数
template<class _Alloc>
struct _Default_allocator_traits
{ _NODISCARD static _CONSTEXPR20_DYNALLOC __declspec(allocator) pointerallocate(_Alloc& _Al, _CRT_GUARDOVERFLOW const size_type _Count) {return _Al.allocate(_Count);}_NODISCARD static _CONSTEXPR20_DYNALLOC __declspec(allocator) pointerallocate(_Alloc& _Al, _CRT_GUARDOVERFLOW const size_type _Count, const const_void_pointer _Hint) {if constexpr (_Has_allocate_hint<_Alloc, size_type, const_void_pointer>::value) {return _Al.allocate(_Count, _Hint);} else {return _Al.allocate(_Count);}}static _CONSTEXPR20_DYNALLOC void deallocate(_Alloc& _Al, pointer _Ptr, size_type _Count) {_Al.deallocate(_Ptr, _Count);}// [6.0] 实现构造行为,可能使用 placement new实现,也可能是其它实现,具体看c++库版本template <class _Ty, class... _Types>static _CONSTEXPR20_DYNALLOC void construct(_Alloc& _Al, _Ty* _Ptr, _Types&&... _Args) {if constexpr (_Uses_default_construct<_Alloc, _Ty*, _Types...>::value) {(void) _Al; // TRANSITION, DevCom-1004719
#ifdef __cpp_lib_constexpr_dynamic_alloc_STD construct_at(_Ptr, _STD forward<_Types>(_Args)...);
#else // __cpp_lib_constexpr_dynamic_alloc::new (static_cast<void*>(_Ptr)) _Ty(_STD forward<_Types>(_Args)...);
#endif // __cpp_lib_constexpr_dynamic_alloc} else {_Al.construct(_Ptr, _STD forward<_Types>(_Args)...);}}template <class _Ty>static _CONSTEXPR20_DYNALLOC void destroy(_Alloc& _Al, _Ty* _Ptr) {if constexpr (_Uses_default_destroy<_Alloc, _Ty*>::value) {
#ifdef __cpp_lib_constexpr_dynamic_alloc_STD destroy_at(_Ptr);
#else // __cpp_lib_constexpr_dynamic_alloc_Ptr->~_Ty();
#endif // __cpp_lib_constexpr_dynamic_alloc} else {_Al.destroy(_Ptr);}}
};
本文依托 MSVC 的源码,讲述了 allocator、list 实现原理以及 allocator_traits 的基本作用,还解答了 rebind 的作用。可以看出这个过程采用了“策略”模式,达到了数据结构与内存管理相解耦的目的。
