stl源码剖析读书笔记之allocator
此系列是本人阅读《STL源码剖析》的一点心得体会,并摘录了部分STL源码加深理解。此篇博客为第二章allocator的读书笔记。
1.construct()和destroy()是全局函数
第二章开始作者定义了一个简单的模板类 JJ::allocator
,有allocator的所有接口,包括 allocator()
deallocate()
construct()
destroy()
四个成员函数 。但是打开 STL源码中的 stl_alloc.h
,SGI STL用的最多的std::alloc
并没有construct()
destroy()
成员函数,SGI STL在这一点上没有遵守STL规范。construct() 和 destroy()被声明为全局函数。
2.destroy()
construct() 和 destroy() 全局函数定义于stl_construct.h
。construct()调用new操作符,没什么好说的,这里主要说说destroy()。
destroy()可以接受一个指针作为参数,调用相应的析构函数,如下所示。
template <class _Tp>
inline void destroy(_Tp* __pointer) {
_Destroy(__pointer);
}
template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
__pointer->~_Tp();
}
这是泛化的函数模板,如果是基本类型的指针,显然无法调用析构函数,书上说有基本类型指针的特化函数模板,但实际上源码里是非模板函数,由于非模板函数优先级高于模板函数,遇到类似int*的指针编译器会调用非模板函数,并且不做任何操作,如下所示。
inline void _Destroy(char*, char*) {}
inline void _Destroy(int*, int*) {}
inline void _Destroy(long*, long*) {}
inline void _Destroy(float*, float*) {}
inline void _Destroy(double*, double*) {}
destroy()还可以接受两个iterator参数,来析构一个区间的所有对象。这就有一个问题,如果这个区间很大,而每个对象的析构函数都是无关紧要的(即trivial destructor,析构函数里什么都不需要做),那么一个个调用析构函数很影响效率。所以需要判断,此对象的析构函数是否是non-trivial。
template <class _ForwardIterator>
inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {
_Destroy(__first, __last);
}
template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
__destroy(__first, __last, __VALUE_TYPE(__first));
}
/*****************萃取__type_traits::has_trivial_destructor***************/
template <class _ForwardIterator, class _Tp>
inline void
__destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
{
typedef typename __type_traits<_Tp>::has_trivial_destructor
_Trivial_destructor;
__destroy_aux(__first, __last, _Trivial_destructor());
}
/*******************如果no-trivial destructor 依次destroy***********************/
template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
for ( ; __first != __last; ++__first)
destroy(&*__first);
}
/*****************如果trivial destructor 不做任何操作*************************/
template <class _ForwardIterator>
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}
因为要在编译期就决定是一个个调用析构函数还是什么都不做,所以不可能用if…else…语法(运行期才去执行),而是依靠template参数推导。如上代码,有两个函数模板__destroy_aux()
,最后一个参数是 _false_type
还是 __true__type
决定了destroy()的操作内容,而这个参数又是从type_traits中萃取出来的,在第3点我们就来讲讲这个type_traits。
这实际上就是泛型编程的常用技巧了,编译期完成的工作不要到运行期再去做,而是依靠template参数推导等技巧来完成类似于if…else…的判断。
3.type_traits
这一部分放在了第三章iterator,在讲完了可以萃取特性的iterator_traits后顺带引出可以萃取更多特性的type_traits,更容易让人理解。不过要在第二章allocator碰到type_traits就要黑人问号了。所以在这里说明一下。
type_traits就是告诉编译器以下信息,这个型别是否一定要执行默认构造函数(has_trivial_default_constructor),是否一定要执行拷贝构造函数( has_trivial_copy_constructor),是否一定要执行赋值运算符(has_trivial_assignment_operator),是否一定要执行析构函数(has_trivial_destructor),是不是POD类型(is_POD_type)。举一个char特化模板的例子如下。
struct __true_type {
};
struct __false_type {
};
__STL_TEMPLATE_NULL struct __type_traits<char> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
本来我也不太理解为什么要用两个struct来表示__false_type
和__true_type
,直到又去回味destroy()源码才明白,仔细看以下两行。
typedef typename __type_traits<_Tp>::has_trivial_destructor
_Trivial_destructor;
__destroy_aux(__first, __last, _Trivial_destructor());
因为是要用template参数推导来确定特化哪一个__destroy_aux(),所以就需要两个class object来让编译器进行参数推导,于是定义了两个空的struct。