前面在介紹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中的函數提供的通用性是很好的,而且源碼的設計與資料結構的實現很精巧,同時也是很複雜的.