1. 程式人生 > >C++ STL原始碼實現以及分析之vector

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