C++ STL源码:vector

目前就容器而言,出现频率最高的当是vector。闲来无事,总结一二,请诸君指点一二...

原汁原味 llvm project

一,vector 构造

vector是一个动态的模板数组,元素类型灵活。

1.1 vector 创建

template <class _Tp, class _Allocator /* = allocator<_Tp> */>
class _LIBCPP_TEMPLATE_VIS vector
{
    _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(size_type __n, const value_type& __x);
};

这是vector其中一个构造函数的申明。平常也经常使用到,比如:

vector<int> coll(10,1); //10个元素的vector,每个元素都是1
_LIBCPP_CONSTEXPR_SINCE_CXX20 :c++20及其之后的标准使能 constexpr 修饰
_LIBCPP_HIDE_FROM_ABI : 接口隐藏

这些宏不是特别影响分析,后面跳过。

1.2 vector 构造

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20
vector<_Tp, _Allocator>::vector(size_type __n, const value_type& __x)
{
    auto __guard = std::__make_exception_guard(__destroy_vector(*this));
    if (__n > 0)
    {
        __vallocate(__n);
        __construct_at_end(__n, __x);
    }
    __guard.__complete();
}

这里介绍下:

1.2.1    异常守卫

auto __guard = std::__make_exception_guard(__destroy_vector(*this));

这里是创建一个异常监控,如果在构造过程中,出现异常,会调用__destroy_vector销毁vector中的元素,并且会产生一个assert 断言错误。一般,进程会出现Native Exeception, 产生tombstone。程序正常运行,在结束的时候,在complete里会关闭断言的产生,vector的对象也就生成了。

1.2.2    空间分配

//  Allocate space for __n objects    
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __vallocate(size_type __n) {
        if (__n > max_size())
            __throw_length_error();
        auto __allocation = std::__allocate_at_least(__alloc(), __n);
        __begin_ = __allocation.ptr;
        __end_ = __allocation.ptr;
        __end_cap() = __begin_ + __allocation.count;
        __annotate_new(0);
    }

大意是分配了n 个object的空间。

1.2.3 构造vector 元素

//  Default constructs __n objects starting at __end_
//  throws if construction throws
//  Precondition:  __n > 0
//  Precondition:  size() + __n <= capacity()
//  Postcondition:  size() == size() + __n
template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20
void
vector<_Tp, _Allocator>::__construct_at_end(size_type __n)
{
    _ConstructTransaction __tx(*this, __n);
    const_pointer __new_end = __tx.__new_end_;
    for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) {
        __alloc_traits::construct(this->__alloc(), std::__to_address(__pos));
    }
}

尾部构造插入元素

其他的形式的构造,大体上也是如此,大同小异。

要点:

1:异常监控机制

2:先分配空间,后才循环尾部插入元素

如果有更多的心得欢迎评论区留言。

二,push_back和emplace_back

2.1 push_back

template <class _Tp, class _Allocator>
_LIBCPP_CONSTEXPR_SINCE_CXX20
inline _LIBCPP_HIDE_FROM_ABI
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>
_LIBCPP_CONSTEXPR_SINCE_CXX20
inline _LIBCPP_HIDE_FROM_ABI
void
vector<_Tp, _Allocator>::push_back(value_type&& __x)
{
    if (this->__end_ < this->__end_cap())
    {
        __construct_one_at_end(std::move(__x));
    }
    else
        __push_back_slow_path(std::move(__x));
}

push_back的重载函数,实现策略一致。目的是调用了:

__construct_one_at_end(__x);

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

函数的目的调用c++其它库函数,construct,构造对象,并插入vector尾部。

__tx是内部类,pos指向的是vector end位置。

2.2 emplace_back

#if _LIBCPP_STD_VER >= 17
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(std::forward<_Args>(__args)...);
    }
    else
        __emplace_back_slow_path(std::forward<_Args>(__args)...);
#if _LIBCPP_STD_VER >= 17
    return this->back();
#endif
}

看起来两个接口基本一致。emplace_back, 使用了完美转发,push_back 使用了move。重要的区别参数不一样:push_back 是value_type 类型

这里是有几个彩蛋值得去试一试,比如:

1:vector 成员的构造函数为什么要是public的。

2:解释了为什么emplace_back 只调用了一次的构造函数。

    完美转发了参数,转到成员的构造函数里了。

push_back 之真假美猴王示例:

#include <iostream>
#include <vector>
using namespace std;

struct Test{
    int m;
    Test(int n = 0):m(n){cout<<this<<",default construct\n";}
    Test(const Test &obj):m(obj.m){cout<<this<<",copy construct\n";}
    Test(Test &&obj):m(obj.m){cout<<this<<",move construct\n";}
};
void func() {
    std::vector<Test> coll;
    std::vector<Test> coll2;
    Test obj1,obj2;
    cout<<"\ntest push_back 1:\n";
    coll.push_back(obj1);
    cout<<"\ntest push_back 2:\n";
    coll.push_back(move(obj2));
    cout<<"\ntest emplace_back:\n";
    coll2.emplace_back(1);
}
int main ()
{
  func();

  return 0;
}

结果:

0x7ffd9c3b1cfc,default construct
0x7ffd9c3b1cf8,default construct

test push_back 1:
0x1a482c0,copy construct

test push_back 2:
0x1a482e4,move construct
0x1a482e0,copy construct

test emplace_back:
0x1a482c0,default construct

示例,展示了push_back ,并不是如同想象中简单把对象push到vector里,真相是构造了一个新的对象放进了vector。这个对象是基于堆上的如同上例。再次使用vector里的对象,已经不是你原来的那个对象了。真假美猴王的彩蛋,get到了吗。

三,其他

   size_type capacity() const _NOEXCEPT
        {return static_cast<size_type>(__end_cap() - this->__begin_);}
    _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI
    bool empty() const _NOEXCEPT
        {return this->__begin_ == this->__end_;}

看起来都是比较简单。通俗易懂。

总结:整个vector stl库里的源码比较易懂。如果进一步深究的应当是allocator的相关特性。

自定义allocator情况极少,这里也不再去追溯。

另外的vector创建的对象是基于堆创建,非栈。

库的实现,需要考虑的情况真多,实现的也很完美,隐藏的知识点也很多。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值