STL之vector

        传统数组定义时需固定长度,vector 是在此基础上的优化,定义时无需指定大小,分配空间可动态增长。定义如下:

template <class _Tp, class _Allocator /* = allocator<_Tp> */>
class _LIBCPP_TEMPLATE_VIS vector
    : private __vector_base<_Tp, _Allocator>
{
private:
    typedef __vector_base<_Tp, _Allocator>           __base;
    ......
}

        自身是个类模板,接收两个参数,一个是数据元素类型,另一个是分配器,默认是 std::allocator,且私有继承模板类 __vector_base

template <class _Tp, class _Allocator>
class __vector_base
    : protected __vector_base_common<true>
{
    ......
protected:
    ......
    pointer                                         __begin_;
    pointer                                         __end_;
    __compressed_pair<pointer, allocator_type> __end_cap_;
    ......
}

        有3个保护型成员:__begin_ 指向数组元素开始节点,__end_ 指向数组元素最后一个节点,__end_cap_ 是一个 pair,其中 first 指向可用空间最后一个节点,并且保护性继承模板类 __vector_base_common

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<int> vec;
vec.push_back(2);
vec.push_back(1);
vec.push_back(3);

        定义 vec 时即调用构造函数 vector() 及继承的父类构造函数:

_LIBCPP_INLINE_VISIBILITY
vector() _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
{
#if _LIBCPP_DEBUG_LEVEL == 2
    __get_db()->__insert_c(this);
#endif
}

template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
__vector_base<_Tp, _Allocator>::__vector_base()
        _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
    : __begin_(nullptr),
      __end_(nullptr),
      __end_cap_(nullptr, __default_init_tag())
{
}

_LIBCPP_INLINE_VISIBILITY __vector_base_common() {}

        主要完成了 __begin_ 等三个数据成员的初始化以及 pair 等相关工作,之后执行 push_back() 方法往 vec 中插入元素:

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

pointer& __end_cap() _NOEXCEPT {return __end_cap_.first();}

        根据方法 __end_cap() 可知,两比较指针开始都指向 nullptr,故调用 __push_back_slow_path() 方法:

template <class _Tp, class _Allocator>
template <class _Up>
void
#ifndef _LIBCPP_CXX03_LANG
vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x)
#else
vector<_Tp, _Allocator>::__push_back_slow_path(_Up& __x)
#endif
{
    allocator_type& __a = this->__alloc();
    __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
    // __v.push_back(_VSTD::forward<_Up>(__x));
    __alloc_traits::construct(__a, _VSTD::__to_address(__v.__end_), _VSTD::forward<_Up>(__x));
    __v.__end_++;
    __swap_out_circular_buffer(__v);
}

        先是定义了个 __split_buffer 模板类类型的对象 __v,主要看传给构造函数的第一个实参 __recommend(size() + 1)

size_type size() const _NOEXCEPT
    {return static_cast<size_type>(this->__end_ - this->__begin_);}

template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
typename vector<_Tp, _Allocator>::size_type
vector<_Tp, _Allocator>::__recommend(size_type __new_size) const
{
    const size_type __ms = max_size();
    if (__new_size > __ms)
        this->__throw_length_error();
    const size_type __cap = capacity();
    if (__cap >= __ms / 2)
        return __ms;
    return _VSTD::max<size_type>(2*__cap, __new_size);
}

        在新申请空间大小不超过最大空间的情况下,将大小定为当前分配空间的两倍,若当前空间已经超过最大可申请空间的一半,则直接将新空间大小定为最大空间。这点可以通过逐步添加元素进行验证,此处不做展示。

        若当前容器仍有可用空间,则调用 __construct_one_at_end() 方法:

  template <class ..._Args>
  _LIBCPP_INLINE_VISIBILITY
  void __construct_one_at_end(_Args&& ...__args) {
    _ConstructTransaction __tx(*this, 1);
    __alloc_traits::construct(this->__alloc(), _VSTD::__to_address(__tx.__pos_),
        _VSTD::forward<_Args>(__args)...);
    ++__tx.__pos_;
  }
};

struct _ConstructTransaction {
    explicit _ConstructTransaction(vector &__v, size_type __n)
      : __v_(__v),  __pos_(__v.__end_), __new_end_(__v.__end_ + __n) {
#ifndef _LIBCPP_HAS_NO_ASAN
      __v_.__annotate_increase(__n);
#endif
    }

        此处并没有空间上的分配,只是对记录位置的属性值有调整。以上是调用 push_back() 方法添加元素的情况,如果删除末尾元素呢?也有对应的 pop_back() 方法:

template <class _Tp, class _Allocator>
inline
void
vector<_Tp, _Allocator>::pop_back()
{
    _LIBCPP_ASSERT(!empty(), "vector::pop_back called for empty vector");
    this->__destruct_at_end(this->__end_ - 1);
}

void __destruct_at_end(pointer __new_last) _NOEXCEPT
{
    __invalidate_iterators_past(__new_last);
    size_type __old_size = size();
    __base::__destruct_at_end(__new_last);
    __annotate_shrink(__old_size);
}

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

        此处只是对 __end_ 值做了减一的调整,对于已分配的空间未做任何处理。也就是说如果分配空间已有128个节点,但即使历经多次删除只剩一个元素时,已分配的空间还是128,不会有任何释放。猜测有以下两点考虑:一是对于业务中曾出现过的空间要求,以后极可能还会再次出现;二是内存资源如今并不十分稀缺。

        若对内存空间的利用率确实有极高的要求,私以为可以在执行 pop_back() 方法时做适当调整,添加释放部分空间的操作。譬如在已用空间仅为已分配空间的 1/3 时,释放 1/3 空间;或者在已用空间为已分配空间的 1/4 时,释放 1/2 空间,以保证当前已分配空间是已用空间的两倍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值