在SGI STL源码中,很多时候一些模板类提供默认形参的时候,都会选择提供deque.
template <class _Tp,
class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) > // stl_stack.h
template <class _Tp,
class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) > // stl_queue.h
_STL_DEPENDENT_DEFAULT_TMPL宏就是替换后扩展为"=deque<_Tp>".既然出场的频率那么高,所以我首先选择他.一起去看看deque的源码.
deque的源码很多,一千六百多行的代码. 而且实现也是蛮复杂的.包括deque_iterator,deque_allocator,deque_base,deque.如果你也在阅读deque的源码,
我的建议是先从deque开始阅读源码,了解成员模板函数的功能. 然后再慢慢向上扩展,到迭代器和内存管理.
还有一点,我的建议是慢慢的迭代,纵向发展,而不是横向.除非迫不得已,一般我还是选择纵向发展.
1. 好吧,题外话说了不少了,现在一起阅读源码. 首先先从deque中的功能函数下手,虽然这些函数很简单.对,我们就是从最简单的下手.
iterator begin() { return _M_start; }
iterator end() { return _M_finish; }
const_iterator begin() const { return _M_start; }
const_iterator end() const { return _M_finish; }
reverse_iterator rbegin() { return reverse_iterator(_M_finish); }
reverse_iterator rend() { return reverse_iterator(_M_start); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(_M_finish); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(_M_start); }
reference front() { return *_M_start; }
reference back() {
iterator __tmp = _M_finish;
--__tmp;
return *__tmp;
}
const_reference front() const { return *_M_start; }
const_reference back() const {
const_iterator __tmp = _M_finish;
--__tmp;
return *__tmp;
}
size_type size() const { return _M_finish - _M_start; }
size_type max_size() const { return size_type(-1); }
bool empty() const { return _M_finish == _M_start; }
我想这些简单的函数还是很好明白的.
2. 接下来该从哪里下手呢? 很多时候面对源码不知道该从什么地方开始,这就是为什么有些人阅读源码畅通无阻,有些人则很吃力.是因为没有使用
合适的方法. 为什么不去看看deque的成员变量呢?
成员变量都是从_Deque_base继承过来的,很容易找得到,找到swap函数就找到了.
void swap(deque& __x) {
__STD::swap(_M_start, __x._M_start);
__STD::swap(_M_finish, __x._M_finish);
__STD::swap(_M_map, __x._M_map);
__STD::swap(_M_map_size, __x._M_map_size);
}
3.接下来我就不再说的那么啰嗦了,也不会再设计到如何阅读源码和详细说明阅读方法的问题了.我们直奔主题吧~
首先来看看deque的设计吧~
deque继承_Deque_base, _Deque_base是骨架,deque派生类只是增加STL提供给我们的封装操作API.
_Deque_base的实现由两个版本,由__STL_USE_STD_ALLOCATORS宏控制.
与_Deque_base相关的是_Deque_Alloc_base.这个模板类也是由__STL_USE_STD_ALLOCATORS控制,而且是和_Deque_base在一起.
deque中的迭代器功能来自于_Deque_iterator.
好吧,现在在源码中出现的类都讲完了. 串一下,deque的功能由上面几个不同的组件拼合而成.
4.看一看STL的内存分配.
由于宏__STL_USE_STD_ALLOCATORS的控制可以使用两个版本的_Deque_alloc_base.所以我们去阅读提供了默认分配策略的那个.
typedef simple_alloc<_Tp, _Alloc> _Node_alloc_type;
typedef simple_alloc<_Tp*, _Alloc> _Map_alloc_type;
_Tp* _M_allocate_node()
{ return _Node_alloc_type::allocate(__deque_buf_size(sizeof(_Tp))); }
void _M_deallocate_node(_Tp* __p)
{ _Node_alloc_type::deallocate(__p, __deque_buf_size(sizeof(_Tp))); }
_Tp** _M_allocate_map(size_t __n)
{ return _Map_alloc_type::allocate(__n); }
void _M_deallocate_map(_Tp** __p, size_t __n)
{ _Map_alloc_type::deallocate(__p, __n); }
simple_alloc就是提供的默认分配策略,allocate与deallocate提供内存的管理策略. 再看另外一个。
template <class _Tp, class _Alloc, bool __is_static>
class _Deque_alloc_base {
public:
typedef typename _Alloc_traits<_Tp,_Alloc>::allocator_type allocator_type;
allocator_type get_allocator() const { return _M_node_allocator; }
_Deque_alloc_base(const allocator_type& __a)
: _M_node_allocator(__a), _M_map_allocator(__a),
_M_map(0), _M_map_size(0)
{}
protected:
typedef typename _Alloc_traits<_Tp*, _Alloc>::allocator_type
_Map_allocator_type;
allocator_type _M_node_allocator;
_Map_allocator_type _M_map_allocator;
_Tp* _M_allocate_node() {
return _M_node_allocator.allocate(__deque_buf_size(sizeof(_Tp)));
}
void _M_deallocate_node(_Tp* __p) {
_M_node_allocator.deallocate(__p, __deque_buf_size(sizeof(_Tp)));
}
_Tp** _M_allocate_map(size_t __n)
{ return _M_map_allocator.allocate(__n); }
void _M_deallocate_map(_Tp** __p, size_t __n)
{ _M_map_allocator.deallocate(__p, __n); }
看一下_Alloc_traits文件源码:
template <class _Tp, class _Tp1>
struct _Alloc_traits<_Tp, allocator<_Tp1> >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, alloc> _Alloc_type;
typedef allocator<_Tp> allocator_type;
};
// Versions for the predefined SGI-style allocators.
template <class _Tp, int __inst>
struct _Alloc_traits<_Tp, __malloc_alloc_template<__inst> >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, __malloc_alloc_template<__inst> > _Alloc_type;
typedef __allocator<_Tp, __malloc_alloc_template<__inst> > allocator_type;
};
template <class _Tp, bool __threads, int __inst>
struct _Alloc_traits<_Tp, __default_alloc_template<__threads, __inst> >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, __default_alloc_template<__threads, __inst> >
_Alloc_type;
typedef __allocator<_Tp, __default_alloc_template<__threads, __inst> >
allocator_type;
};
template <class _Tp, class _Alloc>
struct _Alloc_traits<_Tp, debug_alloc<_Alloc> >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, debug_alloc<_Alloc> > _Alloc_type;
typedef __allocator<_Tp, debug_alloc<_Alloc> > allocator_type;
};
// Versions for the __allocator adaptor used with the predefined
// SGI-style allocators.
template <class _Tp, class _Tp1, int __inst>
struct _Alloc_traits<_Tp,
__allocator<_Tp1, __malloc_alloc_template<__inst> > >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, __malloc_alloc_template<__inst> > _Alloc_type;
typedef __allocator<_Tp, __malloc_alloc_template<__inst> > allocator_type;
};
template <class _Tp, class _Tp1, bool __thr, int __inst>
struct _Alloc_traits<_Tp,
__allocator<_Tp1,
__default_alloc_template<__thr, __inst> > >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, __default_alloc_template<__thr,__inst> >
_Alloc_type;
typedef __allocator<_Tp, __default_alloc_template<__thr,__inst> >
allocator_type;
};
template <class _Tp, class _Tp1, class _Alloc>
struct _Alloc_traits<_Tp, __allocator<_Tp1, debug_alloc<_Alloc> > >
{
static const bool _S_instanceless = true;
typedef simple_alloc<_Tp, debug_alloc<_Alloc> > _Alloc_type;
typedef __allocator<_Tp, debug_alloc<_Alloc> > allocator_type;
};
源码中显示了模板_Alloc_traits以及很多的偏特化版本. 我们发现实际的内存管理功能是 _allocator和simple_alloc提供的.
主要有下面几个模板类:__malloc_alloc_template,
__default_alloc_template,
debug_alloc.
去看看__malloc_alloc_template的实现.
static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}
static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}
可见底层对于内存的管理是通过malloc和realloc,free等标准库函数实现的.
5. 我们讨论一下_Alloc_traits存在的意义.
昨天我看了一下C++ traits技术,这里有资料链接:
Traits: a new and useful template technique by Nathan C. Myers
http://www.cantrip.org/traits.html
很多人讨论C++ traits的时候也习惯成文C++ 萃取技术. 使用_Alloc_traits使对于allocator的使用变得更加灵活,
Container根本不必知道底层的Allocator到底是什么样子的,而是只需要知道他们模糊的样子_Alloc_traits.因为他们
只需要知道内存申请和释放的接口.其他的不关心,同时也没有必要知道.
6.就刚才上面发现如果内存申请或者是重新申请失败的时候就会调用_S_oom_realloc和_S_oom_malloc方法.不妨去看看SGI STL是怎么处理这种内存
申请失败的情况的.
template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
void (* __my_malloc_handler)(); //函数指针. 1
void* __result;
for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler; //handler 2
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
(*__my_malloc_handler)();
__result = malloc(__n);
if (__result) return(__result);
}
}
template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{
void (* __my_malloc_handler)();
void* __result;
for (;;) {
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}
看到上面的源码,是通过在for循环中通过回调实现的.而在__malloc_alloc_template也暴露出一个设置Handler的方法.
static void (* __set_malloc_handler(void (*__f)()))() // 这种方式看上去好别扭.
{
void (* __old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = __f;
return(__old);
}
我在上面注明了,这种方式看上去好别扭,暴露出的接口为什么也要声明成为函数指针的形式.
而本身对Handler的限制是很简单的,甚至都没有参数限制.
static void (* __malloc_alloc_oom_handler)();
我只是理清思路,做到在源码中畅游无阻. 剩下的就是关心那部分的实现,或者是对哪种细节的技术感兴趣.然后针对个人情况去处理.
欢迎对细节提出问题讨论. 技术是讨论出来的~ 另外:欢迎拍砖~