欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

stl源码剖析读书笔记之allocator

程序员文章站 2022-07-12 18:08:08
...

    此系列是本人阅读《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。