C++ Standard Stl — SGI STL源碼學習筆記(07) stl_vector 與 一些問題的細化 3 resize函數剖析

來源:互聯網
上載者:User

  前面在介紹push_back函數的時候有說到placement new的用法.前面說的很簡單.這幾天處理一些其他的事情,直到昨天下午

才有時間看源碼,順便安靜的看一下書. 其中我又看到了掛關於placement new的介紹,那麼就在文章開始之前先說一下這個問題.

  placement new又稱"定為new"(我想這純粹是翻譯過來的意思.),當在禁止使用Heap分配的時候,也就是聲明對象的建構函式

為private的時候,我們不能調用建構函式去構造一個對象,這個時候就可以使用placement new. 前一段時間我在閱讀sig stl源碼的

時候也看到了stl容器對於placement new的使用.

  placement new 的作用是在一個特定的位置放置一個對象,所以不需要調用建構函式,但是卻和建構函式的作用相同. 

需要注意的是placement new並不分配實際的儲存地區, 而是返回一個指向已經分配好空間的指標.所以不要對其執行delete操作.

  但是確實建立了一個對象,如果想要銷毀對象,那麼需要調用嗯對象的解構函式.

       placement大多都是使用在容器技術中,而且是進階技術,也通常用來隱藏技術實現的細節,這裡只做簡單瞭解.

 

前面很多文章都是介紹stl_vector,這篇文章會介紹vector的resize函數,並作為結尾. 先看一下resize函數的源碼:

 

  void resize(size_type __new_size, const _Tp& __x) {    if (__new_size < size())       erase(begin() + __new_size, end());    // 擦除begin()+__new_size -> end()之間的元素    else      insert(end(), __new_size - size(), __x);  }  void resize(size_type __new_size) { resize(__new_size, _Tp()); }  // 這和上面一樣,只不過是提供預設的參數.

 

1. 首先第一點很容易看得出,erase函數執行的是擦除工作,並不能分配記憶體.

2. insert在__new_size >= size()的時候會執行記憶體的重新分配.

 

先看一下erase的源碼:

  iterator erase(iterator __first, iterator __last) {    iterator __i = copy(__last, _M_finish, __first);    destroy(__i, _M_finish);    _M_finish = _M_finish - (__last - __first);    return __first;  }

 跟著copy的源碼走下去,最終會看到最後的實現是:(__copy_trivial)

template <class _Tp>inline _Tp*__copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result) {  memmove(__result, __first, sizeof(_Tp) * (__last - __first));  return __result + (__last - __first);}

 其實上面的erase在resize函數中使用的時候是不會執行copy函數的.因為end() == _M_finish.所以只需要將

   begin() + __new_size -> _M_finish之間的元素都銷毀.destory源碼如下:

inline void _Destroy(_Tp* __pointer) {  __pointer->~_Tp();  // 這裡不是使用delete,而是調用元素對象本身的解構函式.}

  找了很多層,執行很多跳轉最後才找到上面的最終源碼,為什麼stl要這麼麻煩? 因為stl對於容器的記憶體是使用placement new

  前面說過,所以需要調用對象本身的解構函式來完成工作.

 

3. 接下來,我們看看,如果__new_size > size()執行insert函數的情況.insert函數源碼如下:

  void insert (iterator __pos, size_type __n, const _Tp& __x)    { _M_fill_insert(__pos, __n, __x); }

  下面調用到_M_fill_insert函數,這個函數在前面的文章中有講解過.不過當時只講解了該函數的一部分. 本文來看看上半部分. 

template <class _Tp, class _Alloc>void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n,                                          const _Tp& __x){  if (__n != 0) {   // __new_size - size() > 0     if (size_type(_M_end_of_storage - _M_finish) >= __n) {      _Tp __x_copy = __x;      const size_type __elems_after = _M_finish - __position;      iterator __old_finish = _M_finish;      if (__elems_after > __n) {        uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);        _M_finish += __n;        copy_backward(__position, __old_finish - __n, __old_finish);        fill(__position, __position + __n, __x_copy);      }      else {        uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);        _M_finish += __n - __elems_after;        uninitialized_copy(__position, __old_finish, _M_finish);        _M_finish += __elems_after;        fill(__position, __old_finish, __x_copy);      }    }

  在上面的注釋中,只會調用_M_fill_insert函數的前部分,而後半部分的源碼在前面講解過了.

    情況又作為兩種子情況劃分:

      1. size_type(_M_end_of_storage - _M_finish) >   __n(__n = __new_size - size() )

      2. size_type(_M_end_of_storage - _M_finish) <= __n(__n = __new_size - size())

  

對於這兩種情況該如何解釋呢?如何去理解. _M_end_of_storage代表著儲存能力,_M_finish代表著當前的已儲存位置, size()

返回的不是容器的儲存能力,而是當前已經儲存的元素個數. 理解了這些,上面的兩種情況就比較好理解了.  

  

  大的前提條件是:需要resize的新長度已經大於已經儲存的長度.

    對於1情況: __new_size在_M_end_of_storage內. 也就是在儲存能力內.

    對於2情況: 則不在儲存能力範圍內了,需要擴大儲存能力,也就是擴大儲存記憶體單元.

好吧,我們去看看STL源碼是如何處理的.

 

對於情況1:

  if (size_type(_M_end_of_storage - _M_finish) >= __n) {      _Tp __x_copy = __x;      const size_type __elems_after = _M_finish - __position;      iterator __old_finish = _M_finish;      if (__elems_after > __n) {  // 這下面的語句不會執行,因為_M_finish = _position         uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);        _M_finish += __n;        copy_backward(__position, __old_finish - __n, __old_finish);        fill(__position, __position + __n, __x_copy);      }      else {  // 從這裡開始執行.        uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);        _M_finish += __n - __elems_after;                    // _M_finish 向後移動__n-1        uninitialized_copy(__position, __old_finish, _M_finish);           _M_finish += __elems_after;        fill(__position, __old_finish, __x_copy);      }    }

  我們假設容器中儲存的元素類型不是POD,所以追溯源碼就可以找到這裡:

template <class _ForwardIter, class _Size, class _Tp>_ForwardIter__uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,                           const _Tp& __x, __false_type){  _ForwardIter __cur = __first;  __STL_TRY {    for ( ; __n > 0; --__n, ++__cur)      _Construct(&*__cur, __x);    return __cur;  }  __STL_UNWIND(_Destroy(__first, __cur));}

  很容易看得出,就是從_M_finish到後面的_n個位置都使用placement new初始化元素為__x.

  接著查看uninitialized_copy的源碼可以發現並不會執行: 和前面一樣,元素類型同樣不是POD

template <class _InputIter, class _ForwardIter>_ForwardIter __uninitialized_copy_aux(_InputIter __first, _InputIter __last,                         _ForwardIter __result,                         __false_type){  _ForwardIter __cur = __result;  __STL_TRY {    for ( ; __first != __last; ++__first, ++__cur)   // _position == old_position , 所以不會執行.      _Construct(&*__cur, *__first);    return __cur;  }  __STL_UNWIND(_Destroy(__result, __cur));}

  

   好吧,對於情況1的分析,已經很清楚了.下面看看情況2.

   const size_type __old_size = size();              const size_type __len = __old_size + max(__old_size, __n);  // 這裡不是簡單的擴張兩倍.因為不知道new_size和old_size之間的關係.      iterator __new_start = _M_allocate(__len);           // 重新申請記憶體      iterator __new_finish = __new_start;              // 初始化      __STL_TRY {        __new_finish = uninitialized_copy(_M_start, __position, __new_start);  // 將原數組中_M_start -> _M_finish之間的元素複製到新數組中        __new_finish = uninitialized_fill_n(__new_finish, __n, __x);       // 初始化_new_finish -> _new_finish + __n 之間的元素        __new_finish          = uninitialized_copy(__position, _M_finish, __new_finish);           

  情況2執行的代碼相比較情況1要少的多,儘管要執行新的記憶體配置. 在上面的源碼注釋中,我也註明了,這裡不是簡單的擴張為原來的兩倍.

  為什麼要這麼做呢? 原因其實很簡單,因為resize時候,__new_size和size()之間的關係是不知道的,有可能是三倍四倍,也有可能是二倍,

  或者說是介於這些數字之間,所以不應該用一個確切的數字來決定.

  不知道會不會有人問一個問題:  關於記憶體擴張_len的確定為什麼和_M_end_of_storage沒有關係了,就是為什麼和儲存能力沒有關係了。

  而是和size()有關係.  額,答案是這個樣子的.在前面分析兩種情況的時候就說明了,情況2是已經不在儲存範圍內了,所以需要結合這些基本情況

  聯絡在一起考慮.

 

最後對於情況1,情況2都執行相同的源碼:  

    destroy(_M_start, _M_finish);      _M_deallocate(_M_start, _M_end_of_storage - _M_start);      _M_start = __new_start;      _M_finish = __new_finish;      _M_end_of_storage = __new_start + __len;

  執行一些初始化和清理工作,收尾.

  

      到這裡,關於resize函數的介紹就都結束了. 

 

 

  小結:

    STL中容器對於元素的儲存在底層使用的都是數組,而實現資料結構都是使用_M_start,_M_finish,_M_end_of_storage.

    STL中的函數提供的通用性是很好的,而且源碼的設計與資料結構的實現很精巧,同時也是很複雜的. 

 

 

 

 

 

 

 

  

 

 

 

 

  

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.