首先,vector 在VC 2008 中的實現比較復雜,雖然vector 的聲明跟VC6.0 是一致的,如下:
1
2 |
template <
class _Ty,
class _Ax = allocator<_Ty> >
class vector; |
但在VC2008 中vector 還有基類,如下:
1
2 3 4 5 6 7 |
// TEMPLATE CLASS vector template < class _Ty, class _Ax > class vector : public _Vector_val<_Ty, _Ax> { }; |
稍微來看一下基類_Vector_val:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// TEMPLATE CLASS _Vector_val template < class _Ty, class _Alloc > class _Vector_val : public _CONTAINER_BASE_AUX_ALLOC<_Alloc> { // base class for vector to hold allocator _Alval protected: _Vector_val(_Alloc _Al = _Alloc()) : _CONTAINER_BASE_AUX_ALLOC<_Alloc>(_Al), _Alval(_Al) { // construct allocator from _Al } typedef typename _Alloc:: template rebind<_Ty>::other _Alty; _Alty _Alval; // allocator object for values }; |
為了理解_Alty 的類型,還得看一下allocator模板類:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
template<
class _Ty>
class allocator
{ template<> class _CRTIMP2_PURE allocator< void> { // generic allocator for type void public: template< class _Other> struct rebind { // convert an allocator<void> to an allocator <_Other> typedef allocator<_Other> other; }; .... }; ... }; |
typedef typename _Alloc::template rebind<_Ty>::other _Alty; 整體來看是類型定義,假設現在我們這樣使用
vector<int>, 那么_Ty 即 int, _Ax 即 allocator<int>,由vector 類傳遞給 基類_Vector_val,則_Alloc 即
allocator<int> ;可以看到 allocator<void> 是allocator 模板類的特化, rebind<_Ty> 是成員模板類,other是成員模板類
中自定義類型,_Ty 即是int , 那么other 類型也就是allocator<int>, 也就是說_Alty 是類型 allocator<int> 。
_Alty _Alval; 即 基類定義了一個allocator<int> 類型的成員,被vector 繼承后以后用於為vector 里面元素分配內存等操作。
而在VC6.0,_Alval 是直接作為vector 自身的成員存在的。此外還有一個比較大的不同點在於,兩個版本對於capacity 也就是容量的
計算方式不同,接下去的測試可以看到這種不同,在這里可以先說明一下:
VC2008:容量每次增長為原先容量 + 原先容量 / 2;
VC6.0 :容量每次增長為原先容量的2倍。
容量跟vector 大小的概念是不一樣的,capacity 》= size,如下圖所示:
size 指的是avail - data 的區間;capacity 指的是 limit - data 的區間;也就是說存在尚未使用的空間。
下面是模仿VC6.0 中vector 的實現寫的Vec 類,程序主要參考《Accelerated C++》 ,略有修改,比如將接口修改成與VC6.0 一致,
這樣做的好處是可以傳遞第二個參數,也就是說可以自己決定內存的分配管理方式;實現capacity() 函數等;
Vec.h:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
/************************************************************************* > File Name: template_class_Vec.h > Author: Simba > Mail: dameng34@163.com > Created Time: Thu 07 Feb 2013 06:51:12 PM CST ************************************************************************/ #include<iostream> #include<cstddef> #include<memory> #include<algorithm> template < class T, class A_ = std::allocator<T> > class Vec { public: // interface typedef T *iterator; typedef const T *const_iterator; typedef size_t size_type; typedef T value_type; typedef std::ptrdiff_t difference_type; typedef T &reference; typedef const T &const_reference; Vec() { create(); // default constructor } // const T & t = T();意思是默認參數,當沒有傳遞t時,默認使用T() (type T's default constructor) //explicit表示不允許構造函數進行隱式類型轉換 explicit Vec(size_type n, const T &val = T()) { create(n, val); } Vec( const Vec &v) { create(v.begin(), v.end()); // copy constructor } Vec & operator=( const Vec &); // assigment operator ~Vec() { uncreate(); // destructor } size_type size() const { return avail - data; // a value of ptrdiff_t } size_type capacity() const { return (data == 0 ? 0 : limit - data); } T & operator[](size_type i) { return data[i]; } /* because their left operand is different(const), we can overload the operation */ const T & operator[](size_type i) const { return data[i]; } iterator begin() { return data; } const_iterator begin() const { return data; } iterator end() { return avail; } const_iterator end() const { return avail; } void push_back( const T &val) { if (avail == limit) // get space if needed grow(); unchecked_append(val); // append the new element } void clear() { uncreate(); } void empty() { return data == avail; } private: iterator data; // first element in the Vec iterator avail; // one past the last constructed element in the Vec iterator limit; // one past the last available element A_ alloc; // object to handle memory allocation // allocate and initialize the underlying array void create(); void create(size_type, const T &); void create(const_iterator, const_iterator); // destory the element in the array and free the memory void uncreate(); // support functions for push_back void grow(); void unchecked_append( const T &); }; template < class T, class A_> Vec<T, A_> &Vec<T, A_>:: operator=( const Vec<T, A_> &rhs) { // check for self-assigment if (&rhs != this) { uncreate(); create(rhs.begin(), rhs.end()); } return * this; } template < class T, class A_> void Vec<T, A_>::create() { data = avail = limit = 0; } template < class T, class A_> void Vec<T, A_>::create(size_type n, const T &val) { data = alloc.allocate(n); limit = avail = data + n; std::uninitialized_fill(data, limit, val); } template < class T, class A_> void Vec<T, A_>::create(const_iterator i, const_iterator j) { data = alloc.allocate(j - i); limit = avail = std::uninitialized_copy(i, j, data); /* return a pointer to (one past) the last element that it initialized */ } template < class T, class A_> void Vec<T, A_>::uncreate() { if (data) { // destroy(in reverse order) the elements that were constructed iterator it = avail; while (it != data) // destory runs T's destructor for that object, rendering the storage uninitialized again alloc.destroy(--it); alloc.deallocate(data, limit - data); } // reset pointers to indicate that Vec is empty again data = limit = avail = 0; } template < class T, class A_> void Vec<T, A_>::grow() { // when growing, allocate twice as much space as currently in use size_type new_size = std::max( 2 * (limit - data), ptrdiff_t( 1)); // allocate new space and copy elements to the new space iterator new_data = alloc.allocate(new_size); iterator new_avail = std::uninitialized_copy(data, avail, new_data); // return the old space uncreate(); // reset pointers to point to the newly allocated space data = new_data; avail = new_avail; limit = data + new_size; } template < class T, class A_> // error C4519: 僅允許在類模板上使用默認模板參數 void Vec<T, A_>::unchecked_append( const T &val) { alloc.construct(avail++, val); } |
先介紹一下用到的一些類和函數:
allocator 模板類:
1
2 3 4 5 6 7 8 9 10 |
#include <memory>
template < class T> class allocator { public: T *allocate(size_t); void deallocate(T *, size_t); void construct(T *, size_t); void destroy(T *); //....... }; |
當然實際的接口沒實現沒那么簡單,但大概實現的功能差不多:
allocate 調用operator new ;deallocate 調用 operator delete; construct 調用placement new (即在分配好的內
存上調用拷貝構造函數),destroy 調用析構函數。
兩個std函數:
1
2 3 4 5 |
template <
class In,
class For>
For uninitialized_copy(In, In, For); template < class For, class T> void uninitialized_fill(For, For, const T &); |
如 std::uninitialized_copy(i, j, data); 即將i ~ j 指向區間的數值都拷貝到data 指向的區間,返回的是最后一個初始化值的下一個位置。
std::uninitialized_fill(data, limit, val); 即將 data ~ limit 指向的區間都初始化為val 。
為了理解push_back 的工作原理,寫個小程序測試一下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <iostream>
#include "Vec.h" using namespace std; class Test { public: Test() { cout << "Test ..." << endl; } Test( const Test &other) { cout << "copy Test ..." << endl; } ~Test() { cout << "~Test ..." << endl; } }; int main( void) { vector<Test> v2; Test t1; Test t2; Test t3; v2.push_back(t1); v2.push_back(t2); v2.push_back(t3); return 0; } |
從輸出可以看出,構造函數調用3次,拷貝構造函數調用6次,析構函數調用9次,下面來分析一下,首先看下圖:
首先定義t1, t2, t3的時候調用三次構造函數,這個沒什么好說的;接着第一次調用push_back,調用grow進而調用alloc.allocate,
allocate 函數調用operator new 分配一塊內存,第一次uncreate 沒有效果,接着push_back 里面調用uncheck_append,進而調用
alloc.construct,即調用placement new(new (_Vptr) _T1(_Val); ),在原先分配好的內存上調用一次拷貝構造函數。
接着第二次調用push_back,一樣的流程,這次先分配兩塊內存,將t1 拷貝到第一個位置,調用uncreate(),先調用alloc.destroy,即
調用一次析構函數,接着調用alloc.deallcate,即調用operator delete 釋放內存,最后調用uncheck_append將t2 拷貝到第二個位置。
第三次調用push_back,也一樣分配三塊內存,將t1, t2 拷貝下來,然后分別析構,最后將t3 拷貝上去。
程序結束包括定義的三個Test 對象t1, t2, t3 ,析構3次,Vec<Test> v2; v2是局部對象,生存期到則調用析構函數~Vec(); 里面調用
uncreate(), 調用3次Test 對象的析構函數,調用operator delete 釋放3個對象的內存。故總共析構了6次。
在VC2008 中換成 vector<Test> v2; 來測試的話,輸出略有不同,如下:
輸出的次數是一致的,只是拷貝的順序有所不同而已,比如第二次調用push_back 的時候,VC2008 中的vector 是先拷貝t2, 接着拷
貝t1, 然后將t1 釋放掉。
最后再來提一下關於capacity 的計算,如下的測試程序:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream>
#include "Vec.h" using namespace std; int main( void) { Vec< int> v; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; v.push_back( 1); cout << v.capacity() << endl; } |
輸出為 1 2 4 4 8 8 8 即在不夠的情況下每次增長為原來的2倍。
如果換成 vector<int> v; 測試,那么輸出是 1 2 3 4 6 6 9,即在不夠的情況每次增長為原來大小 + 原來大小 / 2;
看到這里,有些朋友會疑惑了,由1怎么會增長到2呢?按照原則不是還是1?其實只要看一下vector 的源碼就清楚了:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void _Insert_n(const_iterator _Where,
size_type _Count, const _Ty &_Val) { // insert _Count * _Val at _Where ..... size_type _Capacity = capacity(); ..... else if (_Capacity < size() + _Count) { // not enough room, reallocate _Capacity = max_size() - _Capacity / 2 < _Capacity ? 0 : _Capacity + _Capacity / 2; // try to grow by 50% if (_Capacity < size() + _Count) _Capacity = size() + _Count; pointer _Newvec = this->_Alval.allocate(_Capacity); pointer _Ptr = _Newvec; ..... } } |
_Insert_n 是被push_back 調用的,當我們試圖增長為_Capacity + _Capacity / 2; 時,下面還有一個判斷:
if (_Capacity < size() + _Count)
_Capacity = size() + _Count;
現在試圖增長為 1 + 1/ 2 = 1; 此時因為 1 < 1 + 1 ; 所以 _Capacity = 1 + 1 = 2;
其他情況都是直接按公式增長。
從上面的分析也可以看出,當push_back 的時候往往帶有拷貝和析構多個操作,所以一下子分配比size() 大的空間capacity,可以減
輕頻繁操作造成的效率問題。
參考:
C++ primer 第四版
Effective C++ 3rd
C++編程規范