C++深入學習:STL源碼剖析 (2) 從源碼深入剖析vector
vector的使用就像數組,但是vector的空間是可變的。vector是動態空間,隨着元素的加入,它的內部機制會自行擴充空間以容納新元素。vector的實現技術,關鍵就在於對大小的控制以及重新配置時的數據移動效率。需要注意的是,對於空間的擴充,是(配置新空間->數據移動->釋放舊空間)的大工程,時間成本是較高的,因此如果每次需要擴充僅僅只擴充所需的,則會導致頻繁的擴充,因此vector採用了未雨綢繆的考慮。
學習vector的核心在於理解 finish和end_of_storage的區別, 即size和capacity的區別,還有如何進行的空間擴充機制,同時,insert函數也處處都是細節,非常值得學習
vector的核心class定義
vector的class定義: (重要和複雜的函數之後詳細展開) (諸多思路都在註釋中)
template<class T,class Alloc=alloc>
class vector{
public:
//用typedef定義一些變量
typedef T value_type;
typedef value_type* pointer; //指針
typedef value_type* iterator; //迭代器
typedef value_type& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef simple_alloc<value_type,Alloc> data_allocator;//空間配置器
/*----------vector的三個核心變量------------------------------*/
iterator start; //目前使用容器的頭
iterator finish; //目前使用空間的尾
iterator end_of_storage; //目前可用空間的尾
/*-------------注意區分finish和end_of_storage-----------------*/
void insert_aux(iterator position,const T&x);
void deallocate();
void fill_initialize(size_type n,const T&value);
iterator allocate_and_fill(size_type n,const T&x){
iterator result=data_allocator::allocate(n);
uninitialized_fill_n(result,n,x);
return result;
}
public:
iterator begin() {return start;} //返回容器頭
iterator end() {return finish;} //返回容器尾
size_type size() const {return size_type(end()-begin());} //返回容器已用大小
size_type capacity() const {return size_type(end_of_storage-begin());}//返回容器真實佔用空間大小
bool empty() const {return begin()==end();} //判斷是否爲空
reference operator[](size_type n) {return *(begin()+n);} //重載[]
reference front(){return *begin();}
reference back(){return *(end()-1);}
/*-------構造函數----------------*/
vector():start(0),finish(0),end_of_storage(0){}
vector(size_type t,const T&value){fill_initialize(n,value);}
vector(int n,const T&value){fill_initialize(n,value);}
vector(long n,const T&value){fill_initialize(n,value);}
explicit vector(size_type n){fill_initialize(n,T());}
//析構函數
~vector(){
destroy(start,finish); //先析構
deallocate(); //再釋放空間
}
//push pop //插入和釋放元素
void push_back(const T& x);
void pop_back();
iterator erase(iterator position); //消除某個位置上的元素
//重新規劃大小
void resize(size_type new_size,const T& x); //非常重要
void resize(size_type new_size){resize(new_size,T());}
void clear(){erase(begin(),end());} //消除空間
}
vector的數據結構
vector維護的是一個連續線性空間,所有不論其元素類型爲何,普通指針都可以作爲vector的迭代器而滿足所有必要條件。vector支持隨機存儲,因此vector提供的是 Random Access Iterators
vector爲了維護連續的線性空間,以兩個迭代器start何finish分別指向配置得來的連續空間目前已經被使用的範圍,並以迭代器end_of_storage指向整個連續空間(含備用空間)的尾端。爲了避免頻繁的空間擴充導致性能下降,vector實際的配置大小可能比客戶端需求量要更大一些,以備將來的擴充,也就是 capacity>=size;一個vector的容量永遠大於或等於其大小,一旦容量等於大小,便滿載了,之後有新增元素就必須重新resize。
通過begin,finish,end_of_storage三個變量,便可以達到提供首位的標識、vector的大小、容量、空容器判斷等諸多機制。
下面這段很簡單代碼說明vector的size和capacity的不同: (g++編譯器)
vector<int> v;
cout << "size:" << v.size() << ",capacity:" << v.capacity() << endl;
for (int i = 0; i < 10;i++)
{
v.push_back(i);
cout << "size:" << v.size() << ",capacity:" << v.capacity() << endl;
}
/*------------------------
size:0,capacity:0
size:1,capacity:1
size:2,capacity:2
size:3,capacity:4
size:4,capacity:4
size:5,capacity:8
size:6,capacity:8
size:7,capacity:8
size:8,capacity:8
size:9,capacity:16
size:10,capacity:16
----------------------*/
vector的核心函數實現機制
tips:爲了簡潔在下面代碼中都沒有加類限定符
resize函數
void resize(size_type new_size,const T& x)
{
if(new_size<size()) //要變的尺寸更小,則把之後多餘的消去
erase(begin()+new_size,end());
else
insert(end(),new_size-size(),x); //用x填充之後增加的數據
}
push_back函數
當向vector尾部插入元素時,首先檢查是否還有備用空間,如果有備用空間則直接在尾部構造元素即可,並調整迭代器finish位置;如果沒有備用空間,則需要擴充空間(重新配置、移動數據、釋放原空間)
void push_back(const T& x)
{
if(finish!=end_of_storage){
construct(finish,x); //在finish處賦值爲x
finish++; //調整finish位置
}
else
{
insert_aux(end(),x); //擴充空間並插入元素
}
}
inser_aux函數
insert_aux函數是protected類型成員,是vector的push_back實現的基礎,即動態擴容的基礎。
動態增加大小,並不是在原空間之後接續新的空間,因爲無法保證原空間之後尚有可配置的空間,而是以原大小的兩倍另外配置一塊較大的空間,然後將內容拷貝過來,在新空間上原內容之後構造新元素,釋放原空間。
因此,注意:對vector的任何操作,一旦引起空間重新配置,則指向原vector的所有指針迭代器都失效。
void insert_aux(iterator position,const T&x)
{
if(finish!=end_of_storage) //後面還有備用空間
{
//則在備用空間出構造一個元素,以vector的最後一個元素作爲初值初始化該新位置的元素值
construct(finish,*(finish-1));
++finish; //調整finish迭代器
//將position到最後的元素向後移動,爲position處騰出位置,然後將position處值置爲x
T x_copy=x;
copy_backward(position,finish-2,finish-1);
*position=x_copy;
}
else //後面沒備用空間,需要擴充
{
//首先需要確定之後申請空間的大小
//g++申請原則:如果原大小爲0,則配置1個元素大小的空間
//如果原大小不爲0,則配置2*原大小的空間
//前半段放原數據,後半段放置新數據,多餘的空餘
const size_type old_size=size();
const size_type len=old_size!=0?2*old_size:1;
//申請新的空間
iterator new_start=data_allocator::allocate(len); //實際配置
iterator new_finish=new_start;
try{
//1.將position之前的值拷貝過來
new_finish=uninitialized_copy(start,position,new_start);
//在position處設置新值
construct(new_finish,x);
++new_finish; //設置新finish
//將position之後的值拷貝過來
new_finish=uninitialized_copy(position,finish,new_finish);
}
catch(...){ //如果出現錯誤,則rollback
//因此擴容過程就像數據庫中常說的 事件操作,即要麼全部完成,要麼全部撤回不做
destroy(new_start,new_finish);
data_allocator::deallocate(new_start,len);
throw;
}
//析構並釋放原來的vector
destroy(begin(),end());
deallocate();
//調整迭代器指向新的vector對應位置
start=new_start;
finish=new_finish;
end_of_storage=new_start+len;
}
}
pop_back函數
pop_back函數不影響vector的capacity,只是size-1。pop_back的過程就是將尾部迭代器向前移動一格,然後將其之前佔據的元素和空間釋放
void pop_back(){
--finish; //將尾部迭代器向前移動
destroy(finish); //釋放
}
erase函數
erase函數用來釋放某一位置或某一區間的所有元素。
iterator erase(iterator first,iterator last)
{
iterator i=copy(last,finish,first); //將後面的元素覆蓋到以first開頭的位置
desrtoy(i,finish);
finish=finish-(last-first);
return first;
}
iterator erase(iterator position)
{
if(position+1!=end()) //position不在末尾
{
copy(position+1,finish,position); //將position之後的元素覆蓋至position爲起始的位置
}
--finish;
destroy(finish);
return position;
}
insert函數
insert函數的實現處處都是細節,果然是大師!
insert函數目的是在position處插入n個x
//將n個元素x插到position位置之前。
//STL規定,區間描述時一律左閉右開(半開半閉),[start, finish)
template<class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
if(n != 0) //元素個數大於0才需要進行操作。
{
////當前空間可容納新元素
if(size_type(end_of_storage - finish) >= n)
{
T x_copy = x;
const size_type elems_after = finish - position;//計算插入點之後的元素個數。
iterator old_finish = finish; //記下原先區間內末尾元素的下一位置。
//插入點之後現有元素個數 大於新增元素個數(n)
if( elems_after > n)
{
//將原區間[finish-n, finish)填充到目標區間[finish, finish+n)。
uninitialized_copy(finish-n, finish, finish);
finish += n; //尾端標記後移。
//將源區間[position,old_finish-n)從逆向填充到目標區間[old_finish-n, old_finish)。
copy_backward(position, old_finish-n, old_finish);
//將目標區間[position, position+n)填充成x_copy。
fill(position, position+n, x_copy);
}
else //插入點之後現有元素個數 小於等於 新增元素個數(n)。
{
//將[finish, finish+(n-elems_after))全部填充成x_copy。
uninitialized_fill_n(finish, n-elems_after, x_copy);
finish += n- elems_after; //尾端標記後移。
//將原區間[position, old_finish)填到目標區間[finish, finish+(old_finish-position))
uninitialized_copy(position, old_finish, finish);
finish += elems_after; //尾端標記後移。
//將目標區間[position, old_finish)填充成x_copy。
fill(position, old_finish, x_copy);
}
}
else //因爲剩餘空間不足,所以需要進行擴容
{
//擴容機制:舊長度的兩倍或者舊長度+新增元素個數 ,取max
const size_type old_size=size();
const size_type len=old_size+max(old_size,n);
//申請新空間
iterator new_start=data_allpcator::allocate(len);
iterator new_finish=new_start;
__STL_TRY{
//將舊的vector的position之前的元素拷貝至新空間
//新增n個新元素
//將舊的vector的position之後的元素拷貝至新空間
new_finish=uninitialized_copy(start, position, new_start);
new_finish=uninitialized_fill_n(new_finish,n,x);
new_finish=uninitialized_copy(position, old_finish, new_finish);
}
#ifdef __STL_USE_EXCEPTIONS
catch() //有異常則rollback,要麼成功,要麼不做
{
destroy(new_start,new_finish);
data_allocator::deallocate(new_start,len);
throw;
}
#endif
//釋放原來空間
destroy(start,finish);
deallocate();
//調整迭代器位置
start=new_start;
finish=new_finish;
end_of_storage=new_start+len;
}
}
}