STL之allocator

来源:互联网 发布:ubuntu和优麒麟哪个好 编辑:程序博客网 时间:2024/05/18 15:04

前言:《STL源码剖析》这本书已经太老了,里面说sgi默认配置器是一个不带参数的alloc,但是通过分析gcc4.7.2编译器(sgi)会发现其实根本就已经没有alloc这个东西了。这本书可以作为学习提纲,具体的代码就不能参照这本书了,必须看编译器的源码。

根据STL的规范,allocator必须要包含以下接口:

allocator::value_type;allocator::size_type;allocator::difference_type;allocator::pointer;allocator::const_pointer;allocator::reference;allocator::const_reference;allocator::rebind::other;allocator::allocator();// default constructorallocator::allocator(const allocator&);template<class U>allocator::allocator(const allocator<U>&);allocator::~allocator();size_type allocator::max_size() const;  pointer allocator::allocate(size_type num_elems, const void* = 0);  void allocator::construct(pointer ptr, const_reference elem);  void allocator::destroy(pointer ptr);  void allocator::deallocate(pointer ptr, size_type useless_n);

记忆方式:

新建一个vector,分配空间    -------    allocate(size_t n, const void* = 0)

push_back(ele)                      -------    construct(pointer ptr, const_reference ele)

pop_back()                              -------    destroy(pointer ptr)

释放所以空间                           -------    deallocate(pointer ptr, size_t useless_n)

deque的T*分配器                   -------     template<class T1> struct rebind { typedef allocator<T1> other; }

container的内存分配与释放、对象构造与析构都是交给allocator去做的。vector要分配内存时,就有allocator的allocate函数去做;当要push_back一个对象到vector中时,由于最后一个位置还没有初始化过,所以需要调用allocator的construct对象在最后一个地址上进行构造;当要pop掉最后一个对象时,只需要调用allocator的destroy函数就好;当要释放整个分配的空间时,调用allocator的deallocate函数。在deque中,不仅需要一个allocator<T>,还需要一个allocator<T*>,所以allocator中还需要有 rebind。

我们可以根据这个规范自己实现一个allocator,然后用于vector,结果证明可以匹配的很好。而且用到的底层API就下面几个:void* operator new(size_t size), new (void* ptr) A(para_lists), void operator delete(void* ptr)。其中 new(void* ptr) A(para_lists) 实际上调用了placement new与A的构造函数,详见文章 “new深入分析”。

在学习deque的过程中,发现deque对外提供了 allocate_node() 等对外接口,通过追踪allocate_node()函数,一探编译器(gcc 4.7.2)allocator的究竟。探索的过程如下:

template<typename _Tp, typename _Alloc>class _Deque_base{        typedef typename _Alloc::template rebind<_Tp>::other  _Tp_alloc_type; // (1)struct _Deque_impl : public _Tp_alloc_type        {_Tp** _M_map;size_t _M_map_size;iterator _M_start;iterator _M_finish;}_Tp* _M_allocate_node()       { return _M_impl._Tp_alloc_type::allocate(__deque_buf_size(sizeof(_Tp))); // (2)       }       _Deque_impl _M_impl;};
_Deque_impl继承了 _Tp_alloc_type,所以(2)行的allocate() 函数实质上是_Tp_alloc_type的。根据(1)行,要知道_Tp_alloc_type的具体类型,需要知道_Deque_base的_Alloc是什么。继续探索,发现如下代码:

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >    class deque : protected _Deque_base<_Tp, _Alloc>
也就是说,allocate()函数其实是 std::allocator<_Tp>::rebind<_Tp>::other的,那么这个又是什么东西呢?继续往下发现

// allocator.htemplate<typename _Tp>class allocator: public __glibcxx_base_allocator<_Tp>{template<typename _Tp1>struct rebind{ typedef allocator<_Tp1> other; };};
原来,std::allocator<_Tp>::rebind<_Tp>::other就是std::allocator<_Tp>;allocator<_Tp>并没有allocate()函数,但是它继承了 __glibcxx_base_allocator<_Tp>,然后又有下面代码

// c++allocator.h#define glibcxx_base_allocator gnu::new_allocator
所以其实deque用的配置器就是 new_allocator。现在我们来看下new_allocator是如何实现的,源码如下:

// new_allocator.htemplate<typename _Tp>class new_allocator{public:typedef size_t     size_type;typedef ptrdiff_t  difference_type;typedef _Tp*       pointer;typedef const _Tp* const_pointer;typedef _Tp&       reference;typedef const _Tp& const_reference;typedef _Tp        value_type;template<typename _Tp1>struct rebind{ typedef new_allocator<_Tp1> other; };new_allocator() _GLIBCXX_USE_NOEXCEPT { }new_allocator(const new_allocator&) _GLIBCXX_USE_NOEXCEPT { }template<typename _Tp1>new_allocator(const new_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT { }~new_allocator() _GLIBCXX_USE_NOEXCEPT { }pointer address(reference __x) const _GLIBCXX_NOEXCEPT{ return std::__addressof(__x); }const_pointer address(const_reference __x) const _GLIBCXX_NOEXCEPT{ return std::__addressof(__x); }// NB: __n is permitted to be 0.  The C++ standard says nothing// about what the return value is when __n == 0.pointer allocate(size_type __n, const void* = 0){ if (__n > this->max_size())  std::__throw_bad_alloc();return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));    <span style="white-space:pre"></span>}// __p is not permitted to be a null pointer.void deallocate(pointer __p, size_type){ ::operator delete(__p); }size_type max_size() const _GLIBCXX_USE_NOEXCEPT{ return size_t(-1) / sizeof(_Tp); }#ifdef __GXX_EXPERIMENTAL_CXX0X__template<typename _Up, typename... _Args>void construct(_Up* __p, _Args&&... __args){ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }template<typename _Up>void destroy(_Up* __p) { __p->~_Up(); }#else// _GLIBCXX_RESOLVE_LIB_DEFECTS// 402. wrong new expression in [some_] allocator::constructvoid construct(pointer __p, const _Tp& __val) { ::new((void *)__p) _Tp(__val); }void destroy(pointer __p) { __p->~_Tp(); }#endif};
可以看出,new_allocator实现了STL规范的所有对外接口。 allocate()函数用到的就是 void* operator new(size_t)函数,construct()函数用到的就是placement new,destroy()函数就是调用对象的析构函数,deallocate()函数就是调用 void operator delete(void* ptr, size_t useless_n)函数。
在STL中(gcc 4.7.2),目前学习过的vector、list、deque都是用的new_allocator作为默认配置器。 





0 0
原创粉丝点击