C++ Standard Stl -- SGI STL源码学习笔记(07) stl_vector 与 一些问题的细化 3 resize函数剖析...
前面在介紹push_back函數(shù)的時候有說到placement new的用法.前面說的很簡單.這幾天處理一些其他的事情,直到昨天下午
才有時間看源碼,順便安靜的看一下書. 其中我又看到了掛關(guān)于placement new的介紹,那么就在文章開始之前先說一下這個問題.
placement new又稱"定為new"(我想這純粹是翻譯過來的意思.),當在禁止使用Heap分配的時候,也就是聲明對象的構(gòu)造函數(shù)
為private的時候,我們不能調(diào)用構(gòu)造函數(shù)去構(gòu)造一個對象,這個時候就可以使用placement new. 前一段時間我在閱讀sig stl源碼的
時候也看到了stl容器對于placement new的使用.
placement new 的作用是在一個特定的位置放置一個對象,所以不需要調(diào)用構(gòu)造函數(shù),但是卻和構(gòu)造函數(shù)的作用相同.
需要注意的是placement new并不分配實際的存儲區(qū)域, 而是返回一個指向已經(jīng)分配好空間的指針.所以不要對其執(zhí)行delete操作.
但是確實創(chuàng)建了一個對象,如果想要銷毀對象,那么需要調(diào)用嗯對象的析構(gòu)函數(shù).
?????? placement大多都是使用在容器技術(shù)中,而且是高級技術(shù),也通常用來隱藏技術(shù)實現(xiàn)的細節(jié),這里只做簡單了解.
?
前面很多文章都是介紹stl_vector,這篇文章會介紹vector的resize函數(shù),并作為結(jié)尾. 先看一下resize函數(shù)的源碼:
?
void resize(size_type __new_size, const _Tp& __x) {if (__new_size < size()) erase(begin() + __new_size, end()); // 擦除begin()+__new_size -> end()之間的元素elseinsert(end(), __new_size - size(), __x);}void resize(size_type __new_size) { resize(__new_size, _Tp()); } // 這和上面一樣,只不過是提供默認的參數(shù).?
1. 首先第一點很容易看得出,erase函數(shù)執(zhí)行的是擦除工作,并不能分配內(nèi)存.
2. insert在__new_size >= size()的時候會執(zhí)行內(nèi)存的重新分配.
?
先看一下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的源碼走下去,最終會看到最后的實現(xiàn)是:(__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函數(shù)中使用的時候是不會執(zhí)行copy函數(shù)的.因為end() == _M_finish.所以只需要將
? ?begin() + __new_size -> _M_finish之間的元素都銷毀.destory源碼如下:
inline void _Destroy(_Tp* __pointer) {__pointer->~_Tp(); // 這里不是使用delete,而是調(diào)用元素對象本身的析構(gòu)函數(shù). }找了很多層,執(zhí)行很多跳轉(zhuǎn)最后才找到上面的最終源碼,為什么stl要這么麻煩? 因為stl對于容器的內(nèi)存是使用placement new
前面說過,所以需要調(diào)用對象本身的析構(gòu)函數(shù)來完成工作.
?
3. 接下來,我們看看,如果__new_size > size()執(zhí)行insert函數(shù)的情況.insert函數(shù)源碼如下:
void insert (iterator __pos, size_type __n, const _Tp& __x){ _M_fill_insert(__pos, __n, __x); }下面調(diào)用到_M_fill_insert函數(shù),這個函數(shù)在前面的文章中有講解過.不過當時只講解了該函數(shù)的一部分. 本文來看看上半部分.
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);}}在上面的注釋中,只會調(diào)用_M_fill_insert函數(shù)的前部分,而后半部分的源碼在前面講解過了.
情況又作為兩種子情況劃分:
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()
返回的不是容器的存儲能力,而是當前已經(jīng)存儲的元素個數(shù). 理解了這些,上面的兩種情況就比較好理解了. ?
大的前提條件是:需要resize的新長度已經(jīng)大于已經(jīng)存儲的長度.
對于1情況: __new_size在_M_end_of_storage內(nèi). 也就是在存儲能力內(nèi).
對于2情況: 則不在存儲能力范圍內(nèi)了,需要擴大存儲能力,也就是擴大存儲內(nèi)存單元.
好吧,我們?nèi)タ纯碨TL源碼是如何處理的.
?
對于情況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) { // 這下面的語句不會執(zhí)行,因為_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 { // 從這里開始執(zhí)行.uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);_M_finish += __n - __elems_after; // _M_finish 向后移動__n-1uninitialized_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的源碼可以發(fā)現(xiàn)并不會執(zhí)行: 和前面一樣,元素類型同樣不是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 , 所以不會執(zhí)行._Construct(&*__cur, *__first);return __cur;}__STL_UNWIND(_Destroy(__result, __cur)); }
? 好吧,對于情況1的分析,已經(jīng)很清楚了.下面看看情況2.
const size_type __old_size = size(); const size_type __len = __old_size + max(__old_size, __n); // 這里不是簡單的擴張兩倍.因為不知道new_size和old_size之間的關(guān)系.iterator __new_start = _M_allocate(__len); // 重新申請內(nèi)存iterator __new_finish = __new_start; // 初始化__STL_TRY {__new_finish = uninitialized_copy(_M_start, __position, __new_start); // 將原數(shù)組中_M_start -> _M_finish之間的元素復制到新數(shù)組中__new_finish = uninitialized_fill_n(__new_finish, __n, __x); // 初始化_new_finish -> _new_finish + __n 之間的元素__new_finish= uninitialized_copy(__position, _M_finish, __new_finish);情況2執(zhí)行的代碼相比較情況1要少的多,盡管要執(zhí)行新的內(nèi)存分配. 在上面的源碼注釋中,我也注明了,這里不是簡單的擴張為原來的兩倍.
為什么要這么做呢? 原因其實很簡單,因為resize時候,__new_size和size()之間的關(guān)系是不知道的,有可能是三倍四倍,也有可能是二倍,
或者說是介于這些數(shù)字之間,所以不應該用一個確切的數(shù)字來決定.
不知道會不會有人問一個問題: ?關(guān)于內(nèi)存擴張_len的確定為什么和_M_end_of_storage沒有關(guān)系了,就是為什么和存儲能力沒有關(guān)系了。
而是和size()有關(guān)系. ?額,答案是這個樣子的.在前面分析兩種情況的時候就說明了,情況2是已經(jīng)不在存儲范圍內(nèi)了,所以需要結(jié)合這些基本情況
聯(lián)系在一起考慮.
?
最后對于情況1,情況2都執(zhí)行相同的源碼:
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;執(zhí)行一些初始化和清理工作,收尾.
? ? ? 到這里,關(guān)于resize函數(shù)的介紹就都結(jié)束了.?
?
?
? 小結(jié):
STL中容器對于元素的存儲在底層使用的都是數(shù)組,而實現(xiàn)數(shù)據(jù)結(jié)構(gòu)都是使用_M_start,_M_finish,_M_end_of_storage.
STL中的函數(shù)提供的通用性是很好的,而且源碼的設計與數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)很精巧,同時也是很復雜的.?
?
?
?
?
?
?
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/respawn/archive/2012/08/03/2621125.html
總結(jié)
以上是生活随笔為你收集整理的C++ Standard Stl -- SGI STL源码学习笔记(07) stl_vector 与 一些问题的细化 3 resize函数剖析...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WdOS源码编译安装MySQL 5.5.
- 下一篇: 一本介绍C指针的书--指针和结构体5.1