C++ STL原始碼實現以及分析之vector
阿新 • • 發佈:2019-01-28
本文主要內容如下:
1. 前篇blogC++ STL空間配置原始碼分析以及實現二介紹了空間配置器allocator
以及vector構造、解構函式的基本實現。
2. 此篇blog主要通過一下幾個方面,說明vector的實現原理
vector
的move
建構函式的定義vector
的erase clear pop_back
三個函式,以及size_t
與ptrdiff_t
的區別vector
的operator[]
操作符過載
分析實現原始碼,其實我們只用實現,理解幾個核心的函式就可以明白其中的原理,並不需要全部的實現。太多實現函式,會讓我們分不清重點,而且看起來頭大。
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_t
與 ptrdiff_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_type
與 difference_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);
}