PoEdu培训 STL班 第五课 STL源码解析之vector(一)
文章类别: 培训笔记 0 评论

PoEdu培训 STL班 第五课 STL源码解析之vector(一)

文章类别: 培训笔记 0 评论

STL源码解析之vector源码(一)

前置条件

  1. 由于Visual Studio版本中带的STL源码中有大量的宏定义和非标准, 丑拒.
  2. 由于SGI STL是权威实现, 本文仅跟踪SGI STL
  3. SGI STL版本3.3, 可在下方下载.

代码跟踪

默认构造函数

首先, 我们找到 stl_vector.h 文件, 可以找到 vector 类.
vector类的定义如下(我直接写上注释):


// vector是一个模板类, 有两个模板参数, 一个类型_Tp, 一个分配器 _Alloc
// 分配器_Alloc 有个默认的值 __STL_DEFAULT_ALLOCATOR(_Tp)

// 这个__STL_DEFAULT_ALLOCATOR 是一个宏定义, 如下:
# ifndef __STL_DEFAULT_ALLOCATOR
#   ifdef __STL_USE_STD_ALLOCATORS
#     define __STL_DEFAULT_ALLOCATOR(T) allocator< T >
#   else
#     define __STL_DEFAULT_ALLOCATOR(T) alloc
#   endif
# endif

// 意思是如果没定义宏 __STL_USE_STD_ALLOCATORS 就用 alloc, 否则用 allocator<T>

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
// vector类protected继承自 _Vector_base 类
class vector : protected _Vector_base<_Tp, _Alloc> 
{
  // 依赖 生成一个static变量
  __STL_CLASS_REQUIRES(_Tp, _Assignable);

private:
  // 简化类型名称为 _Base
  typedef _Vector_base<_Tp, _Alloc> _Base;
public:
  // ====== 以下都是 STL标准 要求的, 必须有 ======

  // 定义 value_type, 其实就是我们自己传递的 _Tp
  typedef _Tp value_type;
  // 将我们的 _Tp* 定义为 pointer
  typedef value_type* pointer;
  // 定义我们的 const _Tp* 定义为 const_pointer
  typedef const value_type* const_pointer;
  // 将 _Tp* 定义为 iterator
  typedef value_type* iterator;
  // 将 const _Tp* 定义为 iterator
  typedef const value_type* const_iterator;
  // 将 _Tp& 定义为 reference
  typedef value_type& reference;
  // 将 const _Tp& 定义为 const_reference
  typedef const value_type& const_reference;
  // 将 size_t 定义为 size_type
  typedef size_t size_type;
  // 将 ptrdiff_t 定义为 difference_type
  // ptrdiff_t 是 一个 int
  typedef ptrdiff_t difference_type;

  // 注意此处的typedef后边跟了一个typename
  // 因为如果不加typename的话, 编译器就会认为 _Base::allocator_type 是本类中的一个静态成员对象
  // 而实际上, 它并不是, 我们只是需要 _Base 中的某个, 所以要加typename
  // 这是来定义我们的 allocator_type, 分配器类型
  typedef typename _Base::allocator_type allocator_type;
  allocator_type get_allocator() const { return _Base::get_allocator(); }

  // 如果有偏特化定义
#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
  // 定义常量反向迭代器
  typedef reverse_iterator<const_iterator> const_reverse_iterator;
  // 定义反向迭代器
  typedef reverse_iterator<iterator> reverse_iterator;
#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
  typedef reverse_iterator<const_iterator, value_type, const_reference, 
                           difference_type>  const_reverse_iterator;
  typedef reverse_iterator<iterator, value_type, reference, difference_type>
          reverse_iterator;
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */

  // ====== 以上都是 STL标准 要求的, 必须有 ======

protected:
// 如果有命名空间 就进行using指令
#ifdef __STL_HAS_NAMESPACES
  // 我们知道 _Base 是一个 typedef的东西, 它其实是 _Vector_base<_Tp, _Alloc>
  using _Base::_M_allocate;
  using _Base::_M_deallocate;
  using _Base::_M_start;
  using _Base::_M_finish;
  using _Base::_M_end_of_storage;
#endif /* __STL_HAS_NAMESPACES */

protected:
  // 两个成员模板, 等用到的时候我们在细看
  void _M_insert_aux(iterator __position, const _Tp& __x);
  void _M_insert_aux(iterator __position);

public:
    // ......
    // 省略....
    // ......

};

我们从类中, 得到vector的默认构造函数:

  explicit vector(const allocator_type& __a = allocator_type())
    : _Base(__a) {}

我们来分析一下这个构造函数:

  1. allocator_type 是一个类型, 是 _Base::alloctor_type 的类型
  2. 这个构造函数拥有默认实参
  3. 默认实参就是 allocator_type 的默认构造函数出来的对象
  4. 它会首先去调用 _Base 的构造, 传递了我们的分配器.

我们在看来一下 _Base 类.

// 类模板
template <class _Tp, class _Alloc> 
class _Vector_base {
public:
  // 将 _Alloc 定义为 allocator_type
  typedef _Alloc allocator_type;
  // 返回一个默认的分配器
  allocator_type get_allocator() const { return allocator_type(); }

  // 构造函数
  // 必须传递一个 分配器 过来
  _Vector_base(const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}

  // 构造函数
  // 按照指定的空间大小和指定的分配器分配内存
  _Vector_base(size_t __n, const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) 
  {
    // 我们注意到, 这里有一个函数是 _M_allocate
    _M_start = _M_allocate(__n);
    _M_finish = _M_start;
    _M_end_of_storage = _M_start + __n;
  }

  // 析构函数
  ~_Vector_base() 
  { 
    // 这里又有一个 _M_deallocate 函数
    _M_deallocate(_M_start, _M_end_of_storage - _M_start); 
  }

protected:
  // 可被继承的成员变量, 是我们指定类型的指针类型
  // 开始
  _Tp* _M_start;
  // 结束
  _Tp* _M_finish;
  // 空间结尾
  _Tp* _M_end_of_storage;

  // 定义一个 分配器
  typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;

  // 这个函数, 可以看到, 是调用上面我们定义的分配器
  // 来进行allocate方法的操作
  _Tp* _M_allocate(size_t __n)
    { return _M_data_allocator::allocate(__n); }
  // 调用我们上面定义的分配器
  // 来进行deallocate方法的操作
  void _M_deallocate(_Tp* __p, size_t __n) 
    { _M_data_allocator::deallocate(__p, __n); }
};

根据以上分析, 我们可以知道, 在我们的 _Base 类中, 有分配内存和回收内存的两个方法.

分配器

在我们的 _Base 类中, 我们知道存在 分配内存 和 释放内存 两个方法, 这两个方法存在于 simple_alloc 这个类中.
这个类位于 stl_alloc.h 中.

所有的SGI STL的内存管理函数, 都在 stl_alloc.h 中定义.
只负责内存的分配和释放, 不负责元素的构造和析构.
它的本质就是operator newoperator delete 的操作.

我们看一看这个类

// 类模板
template<class _Tp, class _Alloc>
class simple_alloc {

public:
    // 静态方法  分配指定大小 __n 个内存
    static _Tp* allocate(size_t __n)
      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
    // 静态方法, 分配一个_Tp类型的内存
    static _Tp* allocate(void)
      { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
    // 静态方法, 释放从 __p 处 指定 __n 大小的内存
    static void deallocate(_Tp* __p, size_t __n)
      { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
    // 静态方法, 释放 __p 的指针
    static void deallocate(_Tp* __p)
      { _Alloc::deallocate(__p, sizeof (_Tp)); }
};

经过这个简单的类, 我们可以知道, 我们的allocate和deallocate都来自我们的 _Alloc
而这个是我们进行指定的, 我们在回过头去看一下调用我们 simple_alloc 类方法的地方:


  // .......

  // 定义一个 分配器
  typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;

  // 这个函数, 可以看到, 是调用上面我们定义的分配器
  // 来进行allocate方法的操作
  _Tp* _M_allocate(size_t __n)
    { return _M_data_allocator::allocate(__n); }
  // 调用我们上面定义的分配器
  // 来进行deallocate方法的操作
  void _M_deallocate(_Tp* __p, size_t __n) 
    { _M_data_allocator::deallocate(__p, __n); }

  // .......

可以看到, 我们的 _Alloc 来自于我们的指定类型.
我们溯源而去, 发现我们的 _Alloc 是在vector进行构造的时候
如果我们没有传递, 就是我们默认的 __STL_DEFAULT_ALLOCATOR(_Tp)
还记得我们的这个宏是什么吗?
在定义 __STL_USE_STD_ALLOCATORS 的时候, 就是 allocator< T >, 否则就是 alloc.

我们现在没有定义 __STL_USE_STD_ALLOCATORS, 所以我们去看一下 alloc


template <bool threads, int inst>
class __default_alloc_template {

private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    // 对齐位数
    enum {_ALIGN = 8};
    // 最大分配字节
    enum {_MAX_BYTES = 128};
    // 可最多分配几次
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
# endif
  // 内存对齐
  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

__PRIVATE:
  // 一个联合体, 用来形成一个"链表", 存储内存池的内存
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
private:
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
        // Specifying a size results in duplicate def for 4.1
# else
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
# endif
  // 获得可用内存的下标
  static  size_t _S_freelist_index(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }

  // Returns an object of size __n, and optionally adds to size __n free list.
  static void* _S_refill(size_t __n);
  // Allocates a chunk for nobjs of size size.  nobjs may be reduced
  // if it is inconvenient to allocate the requested number.
  // 二级分配器 内存池的分配
  // 参数1 大小  参数2 nObjs, 对象个数, 调用的时候 nObjs默认 = 20
  static char* _S_chunk_alloc(size_t __size, int& __nobjs)
  // 如下代码 是我从实现处贴过来的, 本身这里是没有这段代码的
  // 但是这是一个很经典的内存池, 可以进行学习
  {
    char* __result;
    // 计算待分配的总大小
    size_t __total_bytes = __size * __nobjs;
    // 未分配的内存池的大小
    size_t __bytes_left = _S_end_free - _S_start_free;

    // 如果未分配的内存池的大小 >= 待分配的总大小
    if (__bytes_left >= __total_bytes) {
        // 空间足够 直接返回内存地址
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    // 如果未分配的内存池的大小 < 待分配的总大小, 但是 >= 需要分配的一个元素的大小
    // 即 内存池可以分配一个元素出来
    } else if (__bytes_left >= __size) {
        // 求出可以分配的元素个数
        __nobjs = (int)(__bytes_left/__size);
        // 在算出可以分配的元素所占用的总大小
        __total_bytes = __size * __nobjs;
        // 将空闲内存池地址给返回
        __result = _S_start_free;
        // 置内存池的利用状态
        _S_start_free += __total_bytes;
        return(__result);
    // 连一个元素都分配不出了
    } else {
        // 计算出所需要多少空间
        size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        // 尝试利用剩下的一小块内存池
        // 如果还有空闲内存池
        if (__bytes_left > 0) {
            // 将头指向剩余空间地址
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        // 开始进行malloc的内存分配
        _S_start_free = (char*)malloc(__bytes_to_get);
        // 如果分配失败了
        if (0 == _S_start_free) {
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
            _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            // 应该就是重新遍历内存池, 找到一个空闲的内存块然后进行操作.
            // 不是特别确定
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                // 计算一个 __size 的内存池空间地址
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                // 解引用这个地址
                __p = *__my_free_list;
                // 如果这个地址是有效的
                if (0 != __p) {
                    // 重定位内存池分配地址
                    *__my_free_list = __p -> _M_free_list_link;
                    // 做好内存池的各个参数
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    // 递归, 重新进行分配
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                    // 任何剩下的内存片段最终都会使其成为正确的可用内存池列表
                }
            }
            // 如果一个循环完成, 都没有进行重新分配
            _S_end_free = 0;    // In case of exception.    例外的情况
            // 调用一级分配器
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
            // 这应该抛出异常或补救这种情况。 因此,我们假设它成功了。
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs));
    }
  }

  // Chunk allocation state. 内存池分配状态
  // 空闲内存池开始的指针
  static char* _S_start_free;
  // 空闲内存池结束的指针
  static char* _S_end_free;
  // 内存大小
  static size_t _S_heap_size;

# ifdef __STL_THREADS
    static _STL_mutex_lock _S_node_allocator_lock;
# endif

    // It would be nice to use _STL_auto_lock here.  But we
    // don't need the NULL check.  And we do need a test whether
    // threads have actually been started.
    class _Lock;
    friend class _Lock;
    class _Lock {
        public:
            _Lock() { __NODE_ALLOCATOR_LOCK; }
            ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }
    };

public:

  /* __n must be > 0      */
  // 内存分配
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    // 如果需要分配的内存 > 128Byte, 就去调用一级分配器(malloc)
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    // 否则进行内存池的分配
    else {
      // 找到当前内存池的空闲的首地址
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#     endif
      // 将得到的空闲地址进行判定
      _Obj* __RESTRICT __result = *__my_free_list;
      // == 0 说明没有足够的内存池了 开始调用 _S_refill来进行重新分配
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      // 分配成功了 返回内存地址
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

  /* __p may not be 0 */
  // 释放空间
  static void deallocate(void* __p, size_t __n)
  {
    // 如果是 > 128Byte 的内存空间释放, 直接调用一级内存分配器进行释放
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    // 否则进行内存池的内存释放
    else {
      _Obj* __STL_VOLATILE*  __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      _Obj* __q = (_Obj*)__p;

      // acquire lock
#       ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#       endif /* _NOTHREADS */
      // 直接将 __p 的空间加入空闲列表中
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
    }
  }

  // 重新分配
  static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);

} ;

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;

可以看到, alloc分为两级, 二级分配器就是SGI实现的一个内存池
而我们的一级分配器, 本质上是我们的 __default_alloc_template.

我们继续跟踪其中的 allocate 和 deallocate 函数, 可以看到,
它们调用的是 malloc_alloc 类的函数, 我们继续跟入 malloc_alloc


template <int __inst>
class __malloc_alloc_template {

private:

  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
  static void (* __malloc_alloc_oom_handler)();
#endif

public:

  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;
  }

  static void (* __set_malloc_handler(void (*__f)()))()
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }

};

// 一些特化已经省略.....

typedef __malloc_alloc_template<0> malloc_alloc;

此时, 我们知道, alloc::deallocate 是调用的free函数.
而我们的 alloc:allocate 调用的是 _S_oom_malloc
_S_oom_malloc 是一个内部使用的非暴露的函数, 我们找到定义:

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(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 = malloc(__n);
        if (__result) return(__result);
    }
}

可以看到, 最核心的是调用了 malloc 函数.

根据我们的分析, 我们重新回到 vector 的构造函数.
我们知道了, 当我们在进行 vector 的初始化的时候, 会根据我们传递的类型
然后使用我们的分配器进行空间的分配, 这样, 一个 vector 对象就构造完毕了.

其他构造函数

在上面, 我们跟踪了默认构造函数, 我们在来看另外的构造函数:

  vector(size_type __n, const _Tp& __value,
         const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a)
    { _M_finish = uninitialized_fill_n(_M_start, __n, __value); }

  explicit vector(size_type __n)
    : _Base(__n, allocator_type())
    { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); }

  vector(const vector<_Tp, _Alloc>& __x) 
    : _Base(__x.size(), __x.get_allocator())
    { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); }

#ifdef __STL_MEMBER_TEMPLATES
  // Check whether it's an integral type.  If so, it's not an iterator.
  template <class _InputIterator>
  vector(_InputIterator __first, _InputIterator __last,
         const allocator_type& __a = allocator_type()) : _Base(__a) {
    typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
    _M_initialize_aux(__first, __last, _Integral());
  }

  template <class _Integer>
  void _M_initialize_aux(_Integer __n, _Integer __value, __true_type) {
    _M_start = _M_allocate(__n);
    _M_end_of_storage = _M_start + __n; 
    _M_finish = uninitialized_fill_n(_M_start, __n, __value);
  }

  template <class _InputIterator>
  void _M_initialize_aux(_InputIterator __first, _InputIterator __last,
                         __false_type) {
    _M_range_initialize(__first, __last, __ITERATOR_CATEGORY(__first));
  }

#else
  vector(const _Tp* __first, const _Tp* __last,
         const allocator_type& __a = allocator_type())
    : _Base(__last - __first, __a) 
    { _M_finish = uninitialized_copy(__first, __last, _M_start); }
#endif /* __STL_MEMBER_TEMPLATES */

从以上构造函数中, 我们捕获到了如下函数

uninitialized_fill_n

首先分析一下 uninitialized_fill_n 函数, 它在 stl_uninitialized.h 中.

template <class _ForwardIter, class _Size, class _Tp>
inline _ForwardIter 
uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x)
{
  return __uninitialized_fill_n(__first, __n, __x, __VALUE_TYPE(__first));
}

// ...

template <class _ForwardIter, class _Size, class _Tp, class _Tp1>
inline _ForwardIter 
__uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x, _Tp1*)
{
  typedef typename __type_traits<_Tp1>::is_POD_type _Is_POD;
  return __uninitialized_fill_n_aux(__first, __n, __x, _Is_POD());
}

// ...

template <class _ForwardIter, class _Size, class _Tp>
inline _ForwardIter
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                           const _Tp& __x, __true_type)
{
  return fill_n(__first, __n, __x);
}
template <class _ForwardIter, class _Size, class _Tp>
_ForwardIter
__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                           const _Tp& __x, __false_type)
{
  _ForwardIter __cur = __first;
  __STL_TRY {
    for ( ; __n > 0; --__n, ++__cur)
      _Construct(&*__cur, __x);
    return __cur;
  }
  __STL_UNWIND(_Destroy(__first, __cur));
}

// ...
// --------------------------------------------------------------------
// stl_construct.h
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value);
}
template <class _T1>
inline void _Construct(_T1* __p) {
  new ((void*) __p) _T1();
}
// --------------------------------------------------------------------

// ...
// --------------------------------------------------------------------
// stl_algobase.h
template <class _OutputIter, class _Size, class _Tp>
_OutputIter fill_n(_OutputIter __first, _Size __n, const _Tp& __value) {
  __STL_REQUIRES(_OutputIter, _OutputIterator);
  for ( ; __n > 0; --__n, ++__first)
    *__first = __value;
  return __first;
}
// --------------------------------------------------------------------

很简单, 通过层层调用后, 我们发现:

uninitialized_copy

我们在分析一下 uninitialized_copy 函数, 它在 stl_uninitialized.h 中.

template <class _InputIter, class _ForwardIter>
inline _ForwardIter
  uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result)
{
  return __uninitialized_copy(__first, __last, __result,
                              __VALUE_TYPE(__result));
}

// ...

template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter
__uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result, _Tp*)
{
  typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;
  return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}

// ...

template <class _InputIter, class _ForwardIter>
inline _ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __true_type)
{
  return copy(__first, __last, __result);
}

template <class _InputIter, class _ForwardIter>
_ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __false_type)
{
  _ForwardIter __cur = __result;
  __STL_TRY {
    for ( ; __first != __last; ++__first, ++__cur)
      _Construct(&*__cur, *__first);
    return __cur;
  }
  __STL_UNWIND(_Destroy(__result, __cur));
}

// ...

同样的, 通过分析我们发现:

stl_uninitialized.h和stl_construct.h

stl_uninitialized.h 是进行元素值拷贝的.
stl_construct.h 是用来进行容器内元素的构造和析构的.

根据规范, stl_uninitialized.h共暴露如下四个函数:

它们进行直接值的赋值, 或者通过调用 stl_construct.h 中的函数进行元素的构造.
这四个函数都不涉及内存的分配, 只负责元素的值的拷贝/赋值/填充
stl_construct.h 只负责元素的构造和析构, 并不包括内存的分配和释放
它的本质就是 placement newplacement delete 的操作.

push_back函数

来一个常用的 push_back 函数进行跟踪.

  void push_back(const _Tp& __x) {
    // 如果当前的vector的Finish != 当前vector所分配内存空间的 End
    if (_M_finish != _M_end_of_storage) {
      // 利用现有空间进行元素的构造, 执行 placement new
      construct(_M_finish, __x);
      // 将当前vector的Finish后移.
      ++_M_finish;
    }
    // 如果内存不够了, 则进行空间重新分配
    else
      _M_insert_aux(end(), __x);
  }

  
template <class _Tp, class _Alloc>
void 
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
  // 如果当前vector的元素结尾 != vector的内存空间结尾
  if (_M_finish != _M_end_of_storage) {
    // 直接构建一个元素到 end() 的位置
    // 注意, end()始终指向最后一个元素的后一个!
    // 所以这里使用 _M_finish - 1 的值来进行构建
    construct(_M_finish, *(_M_finish - 1));
    // 指针后移
    ++_M_finish;
    _Tp __x_copy = __x;
    // 从后面进行复制, 从最后的元素开始复制,直到首元素复制出来
    // 复制操作是从 last-1 开始, 直到first结束.
    // 这些元素也被从后向前复制到目标容器中, 从 result-1 开始, 一直复制 last-first 个元素
    // 比如 vector 是一个 {1, 2, 3, 4, 5, 6}
    // 我们想得到 { 4, 5, 6, 4, 5, 6 }
    // 可以写如下代码: 
    // copy_backward (v.begin() + 3, v.end(), v.begin() + 3);
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    *__position = __x_copy;
  }
  // 否则
  else {
    const size_type __old_size = size();
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    // 重新分配内存
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    __STL_TRY {
      // 进行元素的拷贝
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      // 构造新元素
      construct(__new_finish, __x);
      ++__new_finish;
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
    }
    // 如果发生异常, 则进行元素的析构, 不会构造出实质性的东西
    // 析构完元素后, 进行空间的释放
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    // 销毁原有的vector的元素
    destroy(begin(), end());
    // 释放原有的vector的内存
    _M_deallocate(_M_start, _M_end_of_storage - _M_start);
    // 重新设置 begin()  end() 等
    _M_start = __new_start;
    _M_finish = __new_finish;
    _M_end_of_storage = __new_start + __len;
  }
}

template <class _Tp, class _Alloc>
void 
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position)
{
  if (_M_finish != _M_end_of_storage) {
    construct(_M_finish, *(_M_finish - 1));
    ++_M_finish;
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    *__position = _Tp();
  }
  else {
    const size_type __old_size = size();
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    __STL_TRY {
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      construct(__new_finish);
      ++__new_finish;
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
    }
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    destroy(begin(), end());
    _M_deallocate(_M_start, _M_end_of_storage - _M_start);
    _M_start = __new_start;
    _M_finish = __new_finish;
    _M_end_of_storage = __new_start + __len;
  }
}

由此可知, 当我们在需要大量push_back的时候, 还是需要预先分配足够多的内存.
当我们进行push_back后, 我们的迭代器需要更新.

小结

  1. typedef后跟typename

    typedef typename _Alty0::template rebind<_Ty>::other _Alty;
 - typename一定要加  
- 如果不加, 编译器会认为typedef后的`是一个类的静态成员对象`  
- 而我们是这个类型中的一个类型, 所以`一定要加typename`   
- 简言之, 在typedef后边有作用域的时候, 一般都需要加上typename
  1. vector在使用 push_back 后, 需要更新迭代器

    • 因为会使内部空间发生改变
  2. 需要注意 end()

    • _M_finish 永远指向一个无效的元素
    • 它是最后一个元素的后一个位置.
    • 所以end()在使用的时候要小心.
  3. 注意STL的浅拷贝问题

    • 如果vector中都装的是指针, 则需要自己管理指针的内存
    • 比如在erase操作的时候, 会擦除指针, 但是不会释放其中的值.

    STL中所有的容器都是 值语义(浅拷贝) 的, 它不会负责元素的生命周期.

    • 因为 值语义可以转换成对象语义(深拷贝)
    • 可以做一个包装, 比如智能指针.

      • 它就是将指针包装成一个类.
    • 进行继承, 包装析构函数

      • 不能特化
      • 因为STL不能修改.
  4. 善用模板的继承

    • 继承可以减少代码膨胀.

      • 模板会生成大量代码, 使用继承可以规避代码膨胀.

    示例:

    
    struct _List_node_base {
      _List_node_base* _M_next;
      _List_node_base* _M_prev;
    };
    
    template <class _Tp>
    struct _List_node : public _List_node_base {
      _Tp _M_data;
    };
    
  5. C++中的new和delete

    C++中有3个new

    • new operator

      • int* p = new int;

        • 1.分配内存
        • 2.调用构造函数
    • operator new

      • string* sz = operator new(sizeof(string));

        • 分配内存
    • placement new

      • new ((void*) p) ClassDemo();
      • new ((ClassDemo*) p) ClassDemo("123");

    同样, C++也有3个delete

    • delete operator
    • operator delete
    • placement delete

    示例:

    
    class Demo
    {
    public:
        Demo()
        {
            std::cout << "Demo()" << std::endl;
        }
        ~Demo()
        {
            std::cout << "~Demo()" << std::endl;
        }
    }
    
    int main()
    {
        // 不会调用构造函数
        Demo* pDemo = (Demo*)(operator new (sizeof(Demo)));
        // 等同于:
        // Demo* pDemo = (Demo*)malloc(sizeof(Demo));
        // 不会调用析构函数的构造
        operator delete(pDemo);
    
        // =================
        // 如下两步之后才能delete
        Demo* pDemo1 = (Demo*)(operator new (sizeof(Demo)));
        new((void*)pDemo1) Demo();
    
        delete pDemo1;
    
    }
  6. 学习SGI STL的异常处理机制

    构造失败后立即析构

    • 构造过程中try
    • 如果异常就进行catch
    • 在catch中进行析构.
  7. SGI STL的内存分配机制

    1. 分配内存
    2. 在现有内存中构建对象
    3. 构建对象后去填充值
    4. 填充完了可以使用
    5. 使用完成, 进行析构
    6. 析构完成后进行内存释放

资料下载

SGI_STL_V3.3.zip

未完待续....

如有错误,请提出指正!谢谢.

回复