C++ STL源码实现以及分析之vector

本文主要内容如下:
1. 前篇blogC++ STL空间配置源码分析以及实现二介绍了空间配置器allocator以及vector构造、析构函数的基本实现。
2. 此篇blog主要通过一下几个方面,说明vector的实现原理
  • vectormove构造函数的定义
  • vectorerase clear pop_back 三个函数,以及size_tptrdiff_t的区别
  • vectoroperator[] 操作符重载
分析实现源码,其实我们只用实现,理解几个核心的函数就可以明白其中的原理,并不需要全部的实现。太多实现函数,会让我们分不清重点,而且看起来头大。

1 move构造函数的定义

下面给出move构造函数的定义(只需要交换内部指针即可):

template <typename T, typename Alloc>
SimpleVec<T, Alloc>::SimpleVec(SimpleVec&& v){
    start_ = v.start_;
    finish_ = v.finish_;
    end_of_storage_ = v.end_of_storage_;
    v.start_ = v.finish_ = v.end_of_storage_ = 0;
}

2 vector erase/clear/pop_back(删除操作)

2.1 pop_back函数最为简单,只需finish_向前移动,并析构对象即可
template<typename T, typename Alloc>
void SimpleVec<T, Alloc>::pop_back(){
    --finish_;
    destroy(finish_);
}
2.2 接下来clear,只析构vector中对象,vector中内存任然保留:
template<typename T, typename Alloc>
void SimpleVec<T, Alloc>::clear(){
    // 仅仅调用对象的析构函数,不释放内存
    destroy(start_, finish_);
    // end_of_storage_不改变,容量还是保留
    finish_ = start_;
}
2.3 erase函数就麻烦点,需要对象的移动
template<typename T, typename Alloc>
//terator SimpleVec<T, Alloc>::erase(iterator position){
typename SimpleVec<T, Alloc>::iterator SimpleVec<T, Alloc>::erase(iterator position){
    erase(position, position + 1);
}

template<typename T, typename Alloc>
typename SimpleVec<T, Alloc>::iterator SimpleVec<T, Alloc>::erase(iterator first, iterator last){
    // 尾部残留对象数
    difference_type len_of_tail = finish_ - last;
    // 删去的对象数目
    difference_type len_of_erase = last - first;
    // 如果len_of_erase < 0 就有问题
    finish_ -= len_of_erase;

    //由前往后赋值
    for (size_t i = 0; i < len_of_tail; ++i) {
        *(first + i) = *(last + i);
    }
    return first;
}

上面的erase函数要注意,必须要先destory [first, last)范围的对象,不然直接安装上面的赋值会导致对象的析构函数没有被调用。

2.4 size_tptrdiff_t的区别
不知道在看vector的源码中,你是否会对ptrdiff_t这个类型有疑问,下面说明下其与size_t的区别。
两个指针相减的结果的类型为ptrdiff_t,它是一种有符号整数类型。减法运算的值为两个指针在内存中的距离(以数组元素的长度为单位,而非字节),因为减法运算的结果将除以数组元素类型的长度。所以该结果与数组中存储的元素的类型无关
size_t是unsigned类型,用于指明数组长度或下标,它必须是一个正数,std::size_t.设计size_t就是为了适应多个平台,其引入增强了程序在不同平台上的可移植性。

ptrdiff_t是signed类型,用于存放同一数组中两个指针之间的差距,它可以使负数,std::ptrdiff_t.同上,使用ptrdiff_t来得到独立于平台的地址差值.

一般在STL中会定义 size_typedifference_type,如下:

typedef size_t     size_type;
typedef ptrdiff_t  difference_type;

下面给出,size_t, ptrdiff_t测试实例如下:

vector<int>    ivec{1, 2, 3, 4, 5, 6};
vector<double> dvec{1, 2, 3, 4, 5, 6};

// 无论double还是int长度都是6
ptrdiff_t i_p1 = ivec.end() - ivec.begin(); // 6
ptrdiff_t i_p2 = ivec.begin() - ivec.end(); // -6
cout<<"ptrdiff_t - i_p1: "<<i_p1<<" i_p2: "<<i_p2<<endl;
// ptrdiff_t - i_p1: 6 i_p2: -6

size_t s_p1 = ivec.end() - ivec.begin(); // 6
size_t s_p2 = ivec.begin() - ivec.end(); // 18446744073709551610
cout<<"size_t -  : s_p1: "<<s_p1<<" s_p2: "<<s_p2<<endl;
// size_t -  : s_p1: 6 s_p2: 18446744073709551610

ptrdiff_t d_p1 = dvec.end() - dvec.begin(); // 6
ptrdiff_t d_p2 = dvec.begin() - dvec.end(); // -6
cout<<"ptrdiff_t -  : d_p1: "<<d_p1<<" d_p2: "<<d_p2<<endl;
// ptrdiff_t -  : d_p1: 6 d_p2: -6

size_t d_s1 = dvec.end() - dvec.begin(); // 6
size_t d_s2 = dvec.begin() - dvec.end(); // 18446744073709551610
cout<<"size_t -  : d_s1: "<<d_s1<<" d_s2: "<<d_s2<<endl;
// size_t -  : d_s1: 6 d_s2: 18446744073709551610

无论是double(字节大小为8)还是int(字节大小为4)容器迭代器的长度差值与字节无关,如下我们可以看到-6 转为 size_t:

size_t s_minus = -6;
cout<<"-6 to size_t is: "<<s_minus<<endl;
// -6 to size_t is: 18446744073709551610

3 vector operator[]

接下来分析operator[], vector中返回的对象有引用版本和非要用版本,如果不返回引用那么=的时候会创建一个临时对象构造新的对象,在一定程度上导致程序性能下降。

stl::vector中声明如下:

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;

定义如下:

reference operator[](const size_t i){
    return *(begin() + i);//begin()

const_reference operator[](const size_t i)const{
    return *(begin() + i); //begin()const
}

注意:上面的operator[]下面的那个实用的是begin()const

iterator begin(){return start_;}
iterator begin()const{return start_;}

stack overflow中有个关于operators []的问题:Why std::vector has 2 operators [] realization ?

reference       operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;

原因如下:

void f(std::vector<int> & v1, std::vector<int> const & v2)
{
    //v1 is non-const vector
    //v2 is const vector

    auto & x1 = v1[0]; //invokes the non-const version
    auto & x2 = v2[0]; //invokes the const version

    v1[0] = 10; //okay : non-const version can modify the object
    v2[0] = 10; //compilation error : const version cannot modify 

    x1 = 10; //okay : x1 is inferred to be `int&`
    x2 = 10; //error: x2 is inferred to be `int const&`
}
声明非const版本好理解,我们定义一个引用到vector对象就可以改变vector中的成员了。
声明const版本,我们定义一个引用到vector对象不改变vector中的成员了。

下面在给出 operator== operator!=的实现:

template<typename T, typename Alloc>
bool SimpleVec<T, Alloc>::operator==(const SimpleVec& v)const{
    if(size() != v.size()){
        return false;
    } else{
        auto p1 = start_;
        auto p2 = v.start_;
        for(;p1 < finish_ && p2 < v.finish_;++p1, ++p2){
            if(*p1 != *p2)return false;
        }
        return true;
    }
};

template<typename T, typename Alloc>
bool SimpleVec<T, Alloc>::operator!=(const SimpleVec& v)const{
    return !(*this == v);
}

https://blog.csdn.net/honpey/article/details/8796386size_tptrdiff_t的区别部分参考)

https://stackoverflow.com/questions/14860622/c-stdvector-operator

猜你喜欢

转载自blog.csdn.net/haluoluo211/article/details/80635782
今日推荐