1. 程式人生 > >SGISTL原始碼閱讀九 Vector容器中

SGISTL原始碼閱讀九 Vector容器中

SGISTL原始碼閱讀九 Vector容器中

前言

在上一篇文章中我們瞭解了Vector的基本結構、構造以及記憶體分配,但是對於Vector的動態性並未涉及。
接下來我們繼續學習vector的一些相關操作,從中我們可以看到vector是如何進行記憶體控制的


深入原始碼

push_back
void push_back(const T& x) {  
	//判斷vector的使用量是否達到最大值
    //如果還有多餘的空間,則直接呼叫construct構造,並且維護vector的迭代器
    if (finish != end_of_storage) {
        construct(finish, x);
        ++finish;
    }
    //否則呼叫insert_aux
    else
        insert_aux(end(), x);
  }
insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
	//判斷vector的使用量是否達到最大值
    //這段程式碼的作用是將傳入的值x放到指定位置(position上去)
    //你可能會疑惑這個if我們在push_back中不是已經判斷了嗎為啥還要判斷一次
    //因為insert_aux不僅僅只會被push_back呼叫
    if (finish != end_of_storage) {
    	//finish指向的是未使用空間的頭, 所以將finish上的值初始化為finish-1上的值,並講finish++
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        //將vector上的數依次往後挪,留出position位置
        copy_backward(position, finish - 2, finish - 1);
        //給position位置賦值
        *position = x_copy;
    }
    //處理容量不夠的情況
    else {
        const size_type old_size = size();
        //這裡處理了舊容量為0的情況
        const size_type len = old_size != 0 ? 2 * old_size : 1;
        //使用空間配置器重新申請一個兩倍大小的空間(或者是1個)
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        __STL_TRY {
        	//初始化操作,將原來的vector拷貝一份到新的空間上
            new_finish = uninitialized_copy(start, position, new_start);
            construct(new_finish, x);
            ++new_finish;
            new_finish = uninitialized_copy(position, finish, new_finish);
    	}
#       ifdef  __STL_USE_EXCEPTIONS
        catch(...) {
        	//處理異常,銷燬拷貝過去的元素,並將空間釋放
            destroy(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
            throw;
        }
#       endif /* __STL_USE_EXCEPTIONS */
		//銷燬元素,釋放原來的空間並且更新vector的迭代器,擴容完成
        destroy(begin(), end());
        deallocate();
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
  }
}

insert_aux中我們清楚地看到了vector對容量的管理,以及那三個迭代器的作用。
只要容量不夠用了,我們就申請一個原vector2倍大小的空間,將原vector的資料拷貝到新的空間後釋放,最後維護vector的三個迭代器。
但是這也有可能會造成迭代器失效的問題,比如說我們定義了一個迭代器指向vector中的元素,vector在我們不知曉的情況下進行了擴容,原來的空間被釋放了,迭代器就指向了一個非法地址。

insert

insert有以下幾個版本

  //指定位置插入一個值為x的元素
  iterator insert(iterator position, const T& x) {
    size_type n = position - begin();
    //如果容量夠用且所插入的位置是最後一個
    if (finish != end_of_storage && position == end()) {
      construct(finish, x);
      ++finish;
    }
    //如果容量不夠用或者插入的位置不是最後一個(insert_aux中處理了這種情況)
    else
      insert_aux(position, x);
    return begin() + n;
  }
  //僅僅指定了位置,則插入一個指定型別的預設構造物件
  iterator insert(iterator position) { return insert(position, T()); }
#ifdef __STL_MEMBER_TEMPLATES
  //範圍插入在指定位置插入一段由first和last迭代器指定範圍的資料
  template <class InputIterator>
  void insert(iterator position, InputIterator first, InputIterator last) {
    //呼叫range_insert(之後也會講到)
    range_insert(position, first, last, iterator_category(first));
  }
#else /* __STL_MEMBER_TEMPLATES */
  //同為範圍插入
  void insert(iterator position,
              const_iterator first, const_iterator last);
#endif /* __STL_MEMBER_TEMPLATES */
  //指定位置插入n個值為x的元素
  void insert (iterator pos, size_type n, const T& x);
  //過載版本
  void insert (iterator pos, int n, const T& x) {
    insert(pos, (size_type) n, x);
  }
  //過載版本
  void insert (iterator pos, long n, const T& x) {
    insert(pos, (size_type) n, x);
  }

總的來說insert操作分為三種

  • 指定位置插入一個元素(在上面的原始碼中我們介紹過了)
  • 指定位置插入n個元素
  • 以迭代器指定範圍插入

指定位置插入n個元素
程式碼和迭代器之情範圍插入極其類似,我們只分析其中之一。
以迭代器指定範圍插入
根據條件編譯分不同的函式,但是實現都是一樣的,range_insert和下面insert的程式碼一致

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position,
                              const_iterator first,
                              const_iterator last) {
  if (first != last) {
    size_type n = 0;
    //呼叫distance求出迭代器指定範圍元素個數
    distance(first, last, n);
    //如果剩餘容量足夠,則將元素插入並維護vector的迭代器
    if (size_type(end_of_storage - finish) >= n) {
      const size_type elems_after = finish - position;
      iterator old_finish = finish;
      if (elems_after > n) {
      	//初始化未初始化空間
        uninitialized_copy(finish - n, finish, finish);
        finish += n;
        //將原vector相應位置向後移動,留出n個位置供插入
        copy_backward(position, old_finish - n, old_finish);
        //插入元素
        copy(first, last, position);
      }
      //如果position的位置剛好在原vector的末尾,則直接插入
      else {
        uninitialized_copy(first + elems_after, last, finish);
        finish += n - elems_after;
        uninitialized_copy(position, old_finish, finish);
        finish += elems_after;
        copy(first, first + elems_after, position);
      }
    }
    //如果剩餘容量不足的情況
    else {
      const size_type old_size = size();
      //申請至少原vector容量的兩倍空間(如果n的值大於old_size則會超過兩倍)
      const size_type len = old_size + max(old_size, n);
      iterator new_start = data_allocator::allocate(len);
      iterator new_finish = new_start;
      __STL_TRY {
        //將原vector頭至插入點position前的元素複製到新空間
        new_finish = uninitialized_copy(start, position, new_start);
        //將需要插入的元素複製到新空間
        new_finish = uninitialized_copy(first, last, new_finish);
        //將原vector剩餘元素賦值到新空間
        new_finish = uninitialized_copy(position, finish, new_finish);
      }
#         ifdef __STL_USE_EXCEPTIONS
      catch(...) {
      	//以下是處理異常
        //將複製了的元素銷燬並且釋放空間
        destroy(new_start, new_finish);
        data_allocator::deallocate(new_start, len);
        throw;
      }
#         endif /* __STL_USE_EXCEPTIONS */
	  //將原vector的元素銷燬並釋放空間,並且維護vector的三個迭代器,完成擴容
      destroy(start, finish);
      deallocate();
      start = new_start;
      finish = new_finish;
      end_of_storage = new_start + len;
      }
  }
}

總結

通過以上的原始碼學習,我們瞭解到了vector的記憶體控制機制,並且講到了vector的插入操作。
可見vector的動態性其實也只是一個假象,需要將原來的空間釋放並且申請一塊更大的空間。
之後我們將繼續學習vector的相關操作。