SGISTL原始碼閱讀九 Vector容器中
阿新 • • 發佈:2018-11-14
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
對容量的管理,以及那三個迭代器的作用。
只要容量不夠用了,我們就申請一個原vector
2倍大小的空間,將原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
的相關操作。