1. 程式人生 > >deque原始碼3(deque的構造與記憶體、ctor、push_back、push_front)

deque原始碼3(deque的構造與記憶體、ctor、push_back、push_front)

deque的構造與記憶體

deque自行定義了兩個專屬的空間配置器:

protected:
    typedef simple_alloc<value_type,Alloc> data_allocator; //專屬空間配置器,每次配置一個元素大小
    typedef simple_alloc<pointer,Alloc> map_allocator; //專屬空間自配器,每次配置一個指標大小

    deque(int n,const value_type& value):
        start(),finish(),map(0_,map_size(
0){ fill_initialize(n,value); }
fill_initialize()負責產生並安排好deque的結構,並將元素的初值設定妥當:
    template <class T,class Alloc,size_t BufSize>
    void deque<T.Alloc,BufSize>::fill_initialize(size_type n,const value_type& value){
        create_map_and_nodes(n); //把deque的結構都產生並安排好
map_pointer cur; __STL_TRY{ //為每個節點的緩衝區設定初值 for(cur=start.node;cur<finish.node;++cur) uninitialized_fill(*cur,*cur+buffer_size(),value): uninitialized_fill(finish.first,finish.cur,value); } catch(...){ ... } }

 其中create_map_and_nodes()負責產生並安排好deque的結構:

template <class T,class Alloc,size_t BufSize>
    void deque<T,Alloc,BufSize>::create_map_and_nodes(size_type num_elements){
        //需要的節點數=(元素個數/每個緩衝區可容納的元素個數)+1
        //如果剛好除整數,會多配置一個節點
        size_type num_nodes=num_elements/buffer_size()+1;
        //一個map要管理幾個節點,最少8個,最多是"所需節點數加2"
        //前後各預備一個,擴充時可用
        map_size=max(inital_map_size(),num_nodes+2);
        map=map_allocator::allocate(map_size); //配置出一個"具有map_size個節點"的map
        //以下令nstart和nfinish指向map所擁有之全部節點的最中央區段
        //保持在最中央,可使頭尾兩端的擴充能量一樣大,每個節點可對應一個緩衝區
        map_pointer nstart=map+(map_size-num_nodes)/2;
        map_pointer nfinish=nstart+num_nodes-1;

        map_pointer cur;
        __STL_TRY{
            //為map內的每個現用節點配置緩衝區,所有緩衝區加起來就是deque的可用空間(最後一個緩衝區可能留有一些富裕)
            for(cur=nstart;cur<=nfinish;++cur)
                *cur=allocate_node();
        }
        catch(...){
            //若成功全部執行,不成功一個都不執行
            ...
            //為deque內的兩個迭代器start和end設定正確內容
            start.set_node(nstart);
            finish.set_node(nfinish);
            start.cur=start.first;
            finish.cur=finish.first+num_element%buffer_size();//整除時,會多配一個節點,cur指向該節點緩衝區的起始處
        }
    }

舉一個例子,程式碼如下,deque狀態如下圖:

deque<int> mydeque(20,0);
for(int i=0;i<mydeque.size();i++)
   mydeque[i]=i;
for(int i=0;i<3;i++)
   mydeque.push_back(i);

push_back()函式內容如下:

public:
    void push_back(const value& t){
        if(finish.cur!=finish.last-1){
            //最後緩衝區上有至少一個備用空間
            construct(finish.cur,t); //直接在備用空間上構造元素
            ++finish.cur; //調整最後緩衝區的使用狀態
        }
        else //最後緩衝區已無元素備用空間或者只有一個元素備用空間
            push_back_aux(t);
    }

接著上面的例子,再在mydeque後面新增一個元素3,由於尾端只存在一個元素的備用空間,所以必須呼叫push_back_aux,先配置一整塊的緩衝區,再新增新的元素,deque狀態如下:

push_back_aux()函式內容如下:

//只有最後一個緩衝區只剩一個備用元素空間時才會被呼叫
    template <class T,class Alloc,size_t BufSize>
    void deque<T,Alloc,BufSize>::push_back_aux(const value_type& t){
        value_type t_copy=t;
        reserve_map_at_back(); //若符合某種條件則必須重換一個map
        *(finish.node+1)=allocate_node(); //配置一個新的節點(緩衝區)
        __STL_TRY{
            construct(finish.cur,t_copy); //針對標的元素設定
            finish.set_node(finish.node+1); //改變finish,令其指向新節點
            finish.cur=finish.first; //設定finish的狀態
        }
        __STL_UNWIND(deallocate_node(*(finish.node+1)));
    }

接著上面的例子,在mydeque的前端插入99,deque狀態如下:

push_front()函式操作如下:

public:
    void push_front(const value& t){
        if(satrt.cur!=start.first){
            //第一緩衝區尚有備用空間
            construct(start.cur-1,t); //直接在備用空間上構造元素
            --start.cur; //調整第一緩衝區的使用狀態
        }
        else //第一緩衝區已無備用空間
            push_front_aux(t);
    }

由上圖可知,這裡必須調用push_front_aux(),push_front_aux()函式操作如下:

//只有第一個緩衝區只剩一個備用元素空間時才會被呼叫
template <class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::push_back_aux(const value_type& t){
    value_type t_copy=t;
    reserve_map_at_front(); //若符合某種條件則必須重換一個map
    *(start.node-1)=allocate_node(); //配置一個新的節點(緩衝區)
    __STL_TRY{
        start.set_node(start.node-1); //改變start,令其指向新節點
        start.cur=start.last-1; //設定start的狀態
        construct(start.cur,t_copy); //針對標的元素設值
    }
    catch(...){
        //若成功全部執行,若失敗全部執行
        start.set_node(start.node+1);
        start.cur=satrt.first;
        deallocate_node(*(start.node-1));
        throw;
    }
}

reserve_map_at_back、reserve_map_at_front

reserve_map_at_back()、reserve_map_at_front()這兩個函式會在什麼時候呼叫?答案是它們會在map需要重新整治的時候,也就是map的節點備用空間不足的時候。

reserve_map_at_front()函式操作如下:

void reserve_map_at_front(size_type nodes_to_add=1){
    if(nodes_to_add>start.node-map) //如果map前端的節點備用空間不足,則必須重新換一個map
        reallocate_map(nodes_to_add,true); //配置更大的,拷貝原來的,釋放原來的
}

reserve_map_at_back()函式操作如下:

void reserve_map_at_back(size_type nodes_to_add=1){
    if(nodes_to_add+1>map_size-(finish.node-map)) //如果map尾端的節點備用空間不足,則必須重新換一個map
        reallocate_map(nodes_to_add,false); 
}

reallocate_map()函式操作如下:

template <class T,class Alloc,size_t BufSize>
void deque<T,Alloc,BufSize>::reallocate_map(size_type nodes_to_add,bool add_at_front){
    size_type old_num_nodes=finish.node-start.node+1;
    size_type new_num_nodes=old_num_nodes+nodes_to_add;
    map_pointer new_nstart;
    if(map_size>2*new_num_nodes){
        new_nstart=map+(map_size-new_num_nodes)/2+(add_at_front?nodes_to_add:0);
        if(new_nstart<start.node)
            copy(start.node,finish.node+1,new_nstart);
        else
            copy_backward(start.node,finish.node+1,new_nstart+old_num_nodes);
    }
    else{
        size_type new_map_size=map_size+max(map_size,node_to_add)+2;
        //配置一塊空間,準備給新map使用
        map_pointer new_map=map_allocator::allocte(new_map_size);
        new_nstart=new_map+(new_map_size-new_num_nodes)/2+(add_at_front?nodes_to_add:0);
        //把原map內容拷貝過來
        copy(start.node,finish.node+1,new_nastart);
        //釋放原map
        map_allocator::deallocate(map,map_size);
        //設定新map的起始地址與大小
        map=new_map;
        map_size=new_map_size;
    }
    //重新設定迭代器start和finish
    start.set_node(new_nstart);
    finish.set_node(new_nstart+old_num_nodes-1);
}