STL源码阅读小记(二)——Vector

前言

最近事情都完成差不多了,看看vecotr实现吧。vector算是stl中很常见的容器了,可能很多人学完c++语法,接触stl就是从vector开始的。vector的数据结构就不细细说明了,相信大家对数组这数据结构绝对都懂。
另外看了标准库才明白要达到Standard Template实际上是很复杂的一件事。一些非常见的函数就只看看声明吧,以及一些类型萃取就不展示了,要完整的展示出来实在太太长了。一份vector<T>加上vector<bool>的源码大概在4000行左右。

博客备份

STL版本

本文所使用的stl版本为libc++ 13.0,属于LLVM项目。

LLVM项目Github

如果遇到不熟悉的宏定义可以参考文档Symbol Visibility Macros

__vector_base_common

在介绍__vector_base_common之前要明确下vector是一个子类。它的继承关系为

__vector_base_common <-- __vector_base <-- vector

__vector_base_common为父类,派生出__vector_base,再派生出vector,其中利用CRTP的编程方法,这个我们后面在说
我们先看看声明

template <bool>
class _LIBCPP_TEMPLATE_VIS __vector_base_common
{
    
    
protected:
    _LIBCPP_INLINE_VISIBILITY __vector_base_common() {
    
    }
    _LIBCPP_NORETURN void __throw_length_error() const;
    _LIBCPP_NORETURN void __throw_out_of_range() const;
};

整个__vector_base_common较为简单,内部除了构造函数外,剩下函数都为抛出错误的函数。
在声明稍后一点的地方会一个比较奇怪的代码。

_LIBCPP_EXTERN_TEMPLATE(class _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS __vector_base_common<true>)

我将_LIBCPP_EXTERN_TEMPLATE这个宏定义找出来。

// Libc++ allows disabling extern template instantiation declarations by
// means of users defining _LIBCPP_DISABLE_EXTERN_TEMPLATE.
//
// Furthermore, when the Debug mode is enabled, we disable extern declarations
// when building user code because we don't want to use the functions compiled
// in the library, which might not have had the debug mode enabled when built.
// However, some extern declarations need to be used, because code correctness
// depends on it (several instances in <locale>). Those special declarations
// are declared with _LIBCPP_EXTERN_TEMPLATE_EVEN_IN_DEBUG_MODE, which is enabled
// even when the debug mode is enabled.
#if defined(_LIBCPP_DISABLE_EXTERN_TEMPLATE)
#   define _LIBCPP_EXTERN_TEMPLATE(...) /* nothing */
#   define _LIBCPP_EXTERN_TEMPLATE_EVEN_IN_DEBUG_MODE(...) /* nothing */
#elif _LIBCPP_DEBUG_LEVEL >= 1 && !defined(_LIBCPP_BUILDING_LIBRARY)
#   define _LIBCPP_EXTERN_TEMPLATE(...) /* nothing */
#   define _LIBCPP_EXTERN_TEMPLATE_EVEN_IN_DEBUG_MODE(...) extern template __VA_ARGS__;
#else
#   define _LIBCPP_EXTERN_TEMPLATE(...) extern template __VA_ARGS__;
#   define _LIBCPP_EXTERN_TEMPLATE_EVEN_IN_DEBUG_MODE(...) extern template __VA_ARGS__;
#endif

简单的来说,在非debug模式,这个宏将被定义为19行的可变参宏。也就是extern template(…)这样的,对模板进行强制的实例化。
关于这样设计我推测可以有效的减少模板带来的代码膨胀,但是又对debug时候不太友好,所才定制了这种策略。

__vector_base

首先我们来看一下声明与内部别名

template <class _Tp, class _Allocator>
class __vector_base
    : protected __vector_base_common<true>{
    
    
public:
    typedef _Allocator                               allocator_type;
    typedef allocator_traits<allocator_type>         __alloc_traits;
    typedef typename __alloc_traits::size_type       size_type;
protected:
    typedef _Tp                                      value_type;
    typedef value_type&                              reference;
    typedef const value_type&                        const_reference;
    typedef typename __alloc_traits::difference_type difference_type;
    typedef typename __alloc_traits::pointer         pointer;
    typedef typename __alloc_traits::const_pointer   const_pointer;
    typedef pointer                                  iterator;
    typedef const_pointer                            const_iterator;
	}

__vector_base是__vector_base_common<true>的子类,两个模板参数一个为内部数据类型与分配器。内部有大量的typedef。
接着我们来看一下__vector_base的成员,__vector_base只有三个。

pointer                                    __begin_;
pointer                                    __end_;
__compressed_pair<pointer, allocator_type> __end_cap_;

__begin_为首地址指针,__end_为数组结束位置指针,size()用此计算大小,\而__end_cap_包含数组最大空间的指针和空间分配器,capacity()函数用此计算大小。这就是vector的核心了成员。

接着整理一下__vector_base中出现我认为比较重要的函数吧,顺带一提其中_LIBCPP_INLINE_VISIBILITY具体含有我找了下StackOverflow。
本意是函数从abi隐藏,在以前是通过inline进行隐藏的,但是现在使用clang的attribute((internal_linkage))进行隐藏。

capacity函数实现

_LIBCPP_INLINE_VISIBILITY
    void clear() _NOEXCEPT {
    
    __destruct_at_end(__begin_);};
	
_LIBCPP_INLINE_VISIBILITY
    size_type capacity() const _NOEXCEPT			//计算空间大小的实现
        {
    
    return static_cast<size_type>(__end_cap() - __begin_);}

copy和move空间分配器

复制或者移动空间分配器类型。

 _LIBCPP_INLINE_VISIBILITY
    void __copy_assign_alloc(const __vector_base& __c, true_type)
        {
    
    
            if (__alloc() != __c.__alloc())
            {
    
    
                clear();	\\析构自己内部数据
                __alloc_traits::deallocate(__alloc(), __begin_, capacity());		\\释放自己的空间
                __begin_ = __end_ = __end_cap() = nullptr;
            }
            __alloc() = __c.__alloc();
        }

_LIBCPP_INLINE_VISIBILITY
    void __move_assign_alloc(__vector_base& __c, true_type)
        _NOEXCEPT_(is_nothrow_move_assignable<allocator_type>::value)
        {
    
    
            __alloc() = _VSTD::move(__c.__alloc());
        }

__alloc()函数作用是返回相应类型的空间分配器

内容析构

template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
void
__vector_base<_Tp, _Allocator>::__destruct_at_end(pointer __new_last) _NOEXCEPT
{
    
    
    pointer __soon_to_be_end = __end_;
    while (__new_last != __soon_to_be_end)
        __alloc_traits::destroy(__alloc(), _VSTD::__to_address(--__soon_to_be_end));	//析构
    __end_ = __new_last;
}

基本上__vector_base就没有什么东西,剩下的都在vector中实现了。

vector

终于到vector的本体了,老规矩,先看声明

template <class _Tp, class _Alloc = allocator<_Tp> >	//声明
class _LIBCPP_TEMPLATE_VIS vector;		

template <class _Tp, class _Allocator /* = allocator<_Tp> */>	//定义
class _LIBCPP_TEMPLATE_VIS vector
    : private __vector_base<_Tp, _Allocator>	//利用CRTP
{
    
    
private:
    typedef __vector_base<_Tp, _Allocator>           __base;
    typedef allocator<_Tp>                           __default_allocator_type;
public:
    typedef vector                                   __self;
    typedef _Tp                                      value_type;
    typedef _Allocator                               allocator_type;
    typedef typename __base::__alloc_traits          __alloc_traits;
    typedef typename __base::reference               reference;
    typedef typename __base::const_reference         const_reference;
    typedef typename __base::size_type               size_type;
    typedef typename __base::difference_type         difference_type;
    typedef typename __base::pointer                 pointer;
    typedef typename __base::const_pointer           const_pointer;
    typedef __wrap_iter<pointer>                     iterator;
    typedef __wrap_iter<const_pointer>               const_iterator;
    typedef _VSTD::reverse_iterator<iterator>         reverse_iterator;
    typedef _VSTD::reverse_iterator<const_iterator>   const_reverse_iterator;

    static_assert((is_same<typename allocator_type::value_type, value_type>::value),	//c++11 编译期断言
                  "Allocator::value_type must be same type as value_type");
}
//顺带一提,这个声明在iosfwd文件中,第一次看的时候没有找到,看的是定义,还纳闷第二个模板参数的默认值是什么。

顺便也想解释下一些vector为什么要套两层的继承,第一次继承,__vector_base_common<true>被声明为extern template减少代码的膨胀,第二次继承利用CRPT,实现编译期的多态。

构造函数

vector构造函数最简单的就是支持InputIterator和_ForwardIterator类型作为参数。至于其他如initializer_list等为参数类型的构造函数就不一一列出了。这里以InputIterator版本为例。

template <class _InputIterator>
        vector(_InputIterator __first,
               typename enable_if<__is_cpp17_input_iterator  <_InputIterator>::value &&
                                 !__is_cpp17_forward_iterator<_InputIterator>::value &&
                                 is_constructible<
                                    value_type,
                                    typename iterator_traits<_InputIterator>::reference>::value,
                                 _InputIterator>::type __last);

如果刚入门c++看这个必然头大,不过我们分解一些就很好理解了。
首先要认清这个构造函数只有两个参数,只不过第二个参数的类型长了一点,是因为这里做了个类型检查。我们重点分解析第二个参数。

typename enable_if<__is_cpp17_input_iterator  <_InputIterator>::value &&
                                 !__is_cpp17_forward_iterator<_InputIterator>::value &&
                                 is_constructible<
                                    value_type,
                                    typename iterator_traits<_InputIterator>::reference>::value,
                                 _InputIterator>::type __last

第二个参数名称为__last,实际上也是个_InputIterator类型。首先先enable_if,enable_if有两个模板参数,第一个为bool值,第二个为类型,只有当第一个模板参数为true时候,enable_if<>::type为第二个参数的类型。最外面enable_if是一个类型检查,如果类型不对,则编译报错,接下来我们看enable_if的第一个模板参数

__is_cpp17_input_iterator  <_InputIterator>::value &&	//判断是否是input_iterator
!__is_cpp17_forward_iterator<_InputIterator>::value &&	//判断是否是forward_iterator
		 is_constructible<								//利用类型萃取,判断是否存在class A(A &parm)拷贝构造函数
		value_type,
		typename iterator_traits<_InputIterator>::reference>::value

另一个构造函数也是差不多如此,就不细说了。

template <class _InputIterator>
        vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a,
               typename enable_if<__is_cpp17_input_iterator  <_InputIterator>::value &&
                                 !__is_cpp17_forward_iterator<_InputIterator>::value &&
                                 is_constructible<
                                    value_type,
                                    typename iterator_traits<_InputIterator>::reference>::value>::type* = 0);

插入函数

插入函数就两个push_back和emplace_back。先看下emplace_back吧

emplace_back

//if _LIBCPP_STD_VER > 14,实际上这里是用宏控制c++11和c++14两个版本,14和17区别就是是否返回自身的引用
template <class _Tp, class _Allocator>
template <class... _Args>
inline
#if _LIBCPP_STD_VER > 14
typename vector<_Tp, _Allocator>::reference
#else
void
#endif
vector<_Tp, _Allocator>::emplace_back(_Args&&... __args)
{
    
    
    if (this->__end_ < this->__end_cap())
    {
    
    
        __construct_one_at_end(_VSTD::forward<_Args>(__args)...);
    }
    else
        __emplace_back_slow_path(_VSTD::forward<_Args>(__args)...);
#if _LIBCPP_STD_VER > 14
    return this->back();
#endif
}

可以看到emplace_back的参数是一个折叠的右值引用,这里用右值引用是利用了折叠引用的特性,然后再使用foward进行参数转发。
在内部空间足够存储的情况下调用__construct_one_at_end()函数,如果不足则调用__emplace_back_slow_path;

首先我们来看下__construct_one_at_end()函数,这个函数在可用空间原地构造。

  template <class ..._Args>
  _LIBCPP_INLINE_VISIBILITY
  void __construct_one_at_end(_Args&& ...__args) {
    
    
    _ConstructTransaction __tx(*this, 1);	//简单的来说这个结构体是用来移动__end指针的,参数1就是向后移动1个距离。
    __alloc_traits::construct(this->__alloc(), _VSTD::__to_address(__tx.__pos_),
        _VSTD::forward<_Args>(__args)...);
    ++__tx.__pos_;
  }
};

其中定义了一个_ConstructTransaction结构体,用来操作vector内部的指针。然后使用__alloc_traits进行placement_new。参数…__args为构造函数的参数,特别注意下__args利用了可变参模板和折叠表达式来匹配构造函数的参数。
接着看空间不足时候的__emplace_back_slow_path()函数

template <class _Tp, class _Allocator>
template <class... _Args>
void
vector<_Tp, _Allocator>::__emplace_back_slow_path(_Args&&... __args)
{
    
    
    allocator_type& __a = this->__alloc();
    __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);	
	\\__recommend(size() + 1)为最大cap,在未达到空间分配器上限时为乘以2size()为现在要构造的位置,__a为分配器
//    __v.emplace_back(_VSTD::forward<_Args>(__args)...);
    __alloc_traits::construct(__a, _VSTD::__to_address(__v.__end_), _VSTD::forward<_Args>(__args)...);	//在新的空间上构造
    __v.__end_++;
    __swap_out_circular_buffer(__v);	//交换
}

这里就是比较经典两倍扩容了,__split_buffer构造出两倍的空间,接着__alloc_traits::construct构造,最后使用__swap_out_circular_buffer交换回来。关于__split_buffer,我推测是一个多种容器通用的split_buffer,用来解决容器扩容,内部有对多种迭代器以及指针的构造函数。

push_back

push_back其实和emplace_back基本差不多,只不过是制作了左值和右值两个版本。

//左值版本
template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
void
vector<_Tp, _Allocator>::push_back(const_reference __x)
{
    
    
    if (this->__end_ != this->__end_cap())
    {
    
    
        __construct_one_at_end(__x);
    }
    else
        __push_back_slow_path(__x);
}
//右值版本
template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
void
vector<_Tp, _Allocator>::push_back(value_type&& __x)
{
    
    
    if (this->__end_ < this->__end_cap())
    {
    
    
        __construct_one_at_end(_VSTD::move(__x));
    }
    else
        __push_back_slow_path(_VSTD::move(__x));
}

用到的两个辅助函数和emplace_back一样。

猜你喜欢

转载自blog.csdn.net/ninesnow_c/article/details/121947014
今日推荐