C++深入學習:STL源碼剖析 (2) 從源碼深入剖析vector

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

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章