首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:
<nobr>1<br> 2<br></nobr>
|
template<
class_Ty,
class_Ax=allocator<_Ty>>
classvector; |
但在VC2008 中vector 还有基类,如下:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br></nobr>
|
//TEMPLATECLASSvector template< class_Ty, class_Ax> classvector : public_Vector_val<_Ty,_Ax> { }; |
稍微来看一下基类_Vector_val:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br> 18<br> 19<br></nobr>
|
//TEMPLATECLASS_Vector_val template< class_Ty, class_Alloc> class_Vector_val : public_CONTAINER_BASE_AUX_ALLOC<_Alloc> { //baseclassforvectortoholdallocator_Alval protected: _Vector_val(_Alloc_Al=_Alloc()) :_CONTAINER_BASE_AUX_ALLOC<_Alloc>(_Al),_Alval(_Al) { //constructallocatorfrom_Al } typedef typename_Alloc:: template rebind<_Ty>::other_Alty; _Alty_Alval; //allocatorobjectforvalues }; |
为了理解_Alty 的类型,还得看一下allocator模板类:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br></nobr>
|
template<
class_Ty>
classallocator
{ template<> class_CRTIMP2_PUREallocator< void> { //genericallocatorfortypevoid public: template< class_Other> structrebind { //convertanallocator<void>toanallocator<_Other> typedefallocator<_Other>other; }; .... }; ... }; |
typedeftypename_Alloc::templaterebind<_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 里面元素分配内存等操作。
如iteratornew_dataalloc.allocate(new_size); 注意,标准的vector::iterator 是以模板类实现的,下面的实现简单地将其等同为指针,
实际上真正的iterator 类的实现是内部有一个指针成员,指向容器元素。
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
对比list 的实现,继承它的基类_List_nod 的一个成员 allocator<_Node> _Alnod; 如下:
typename _Alloc::template rebind<_Node>::other _Alnod; // allocator object for nodes
其中 _Node有三个成员,如下:
_Nodeptr _Next; // successor node, or first element if head
_Nodeptr _Prev; // predecessor node, or last element if head
_Ty _Myval; // the stored value, unused if head
如果是list<int> ,那么_Ty 即是int 类型。双向链表在创建一个新结点时,这样实现:
_Nodeptr _Pnode = this->_Alnod.allocate(1); // 即分配一个节点的空间,返回指向这个节点的指针。
实际上list 还继承另外两个基类的两个成员,如下:
typename _Alloc::template rebind<_Nodeptr>::other _Alptr;// allocator object for pointers to nodes
typename _Alloc::template rebind<_Ty>::other _Alty _Alval; // allocator object for values stored in nodes
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
在VC6.0 vector 的实现,_Alval 是直接作为vector 自身的成员存在的。此外还有一个比较大的不同点在于,两个版本对于capacity 也就是容量的
计算方式不同,接下去的测试可以看到这种不同,在这里可以先说明一下:
VC2008:容量每次增长为原先容量 + 原先容量 / 2;
VC6.0 :容量每次增长为原先容量的2倍。
容量跟vector 大小的概念是不一样的,capacity 》= size,如下图所示:
size 指的是avail - data 的区间;capacity 指的是 limit - data 的区间;也就是说存在尚未使用的空间。
******************************************************************************************************************************************************
data, avail 和 limit 是vector 类的三个指针成员,对比list 的实现,自身也许只有两个成员,如下:
Nodeptr _Myhead; // pointer to head node
size_type _Mysize; // number of elements
*******************************************************************************************************************************************************
下面是模仿VC6.0 中vector 的实现写的Vec 类,程序主要参考《Accelerated C++》 ,略有修改,比如将接口修改成与VC6.0 一致,
这样做的好处是可以传递第二个参数,也就是说可以自己决定内存的分配管理方式;实现capacity() 函数等;
Vec.h:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br> 18<br> 19<br> 20<br> 21<br> 22<br> 23<br> 24<br> 25<br> 26<br> 27<br> 28<br> 29<br> 30<br> 31<br> 32<br> 33<br> 34<br> 35<br> 36<br> 37<br> 38<br> 39<br> 40<br> 41<br> 42<br> 43<br> 44<br> 45<br> 46<br> 47<br> 48<br> 49<br> 50<br> 51<br> 52<br> 53<br> 54<br> 55<br> 56<br> 57<br> 58<br> 59<br> 60<br> 61<br> 62<br> 63<br> 64<br> 65<br> 66<br> 67<br> 68<br> 69<br> 70<br> 71<br> 72<br> 73<br> 74<br> 75<br> 76<br> 77<br> 78<br> 79<br> 80<br> 81<br> 82<br> 83<br> 84<br> 85<br> 86<br> 87<br> 88<br> 89<br> 90<br> 91<br> 92<br> 93<br> 94<br> 95<br> 96<br> 97<br> 98<br> 99<br> 100<br> 101<br> 102<br> 103<br> 104<br> 105<br> 106<br> 107<br> 108<br> 109<br> 110<br> 111<br> 112<br> 113<br> 114<br> 115<br> 116<br> 117<br> 118<br> 119<br> 120<br> 121<br> 122<br> 123<br> 124<br> 125<br> 126<br> 127<br> 128<br> 129<br> 130<br> 131<br> 132<br> 133<br> 134<br> 135<br> 136<br> 137<br> 138<br> 139<br> 140<br> 141<br> 142<br> 143<br> 144<br> 145<br> 146<br> 147<br> 148<br> 149<br> 150<br> 151<br> 152<br> 153<br> 154<br> 155<br> 156<br> 157<br> 158<br> 159<br> 160<br> 161<br> 162<br> 163<br> 164<br> 165<br> 166<br> 167<br> 168<br> 169<br> 170<br> 171<br> 172<br> 173<br> 174<br> 175<br> 176<br> 177<br> 178<br> 179<br> 180<br> 181<br> 182<br> 183<br> 184<br> 185<br> 186<br> 187<br> 188<br> 189<br> 190<br> 191<br></nobr>
|
/*************************************************************************
>FileName:template_class_Vec.h >Author:Simba >Mail:[email protected] >CreatedTime:Thu07Feb201306:51:12PMCST ************************************************************************/ #include<iostream> #include<cstddef> #include<memory> #include<algorithm> template< classT, classA_=std::allocator<T>> classVec { public: //interface typedefT*iterator; typedef constT*const_iterator; typedefsize_tsize_type; typedefTvalue_type; typedefstd::ptrdiff_tdifference_type; typedefT&reference; typedef constT&const_reference; Vec() { create(); //defaultconstructor } //constT&t=T();意思是默认参数,当没有传递 参数时,T() 即定义一个类型为T的无名对象(参数为空会调用默认构造函数)
// 也就是 t 引用着这个无名对象。
//explicit表示不允许构造函数进行隐式类型转换 explicitVec(size_typen, constT&val=T()) { create(n,val); } Vec( constVec&v) { create(v.begin(),v.end()); //copyconstructor } Vec& operator=( constVec&); //assigmentoperator ~Vec() { uncreate(); //destructor } size_typesize() const { returnavail-data; //avalueofptrdiff_t } size_typecapacity() const { return(data== 0? 0:limit-data); } T& operator[](size_typei) { returndata[i]; } /*becausetheirleftoperandisdifferent(const),wecanoverloadtheoperation*/ constT& operator[](size_typei) const { returndata[i]; } iteratorbegin() { returndata; } const_iteratorbegin() const { returndata; } iteratorend() { returnavail; } const_iteratorend() const { returnavail; } voidpush_back( constT&val) { if(avail==limit) //getspaceifneeded grow(); unchecked_append(val); //appendthenewelement } voidclear() { uncreate(); } voidempty() { returndata==avail; } private: iteratordata; //firstelementintheVec iteratoravail; //onepastthelastconstructedelementintheVec iteratorlimit; //onepastthelastavailableelement A_alloc; //objecttohandlememoryallocation //allocateandinitializetheunderlyingarray voidcreate(); voidcreate(size_type, constT&); voidcreate(const_iterator,const_iterator); //destorytheelementinthearrayandfreethememory voiduncreate(); //supportfunctionsforpush_back voidgrow(); voidunchecked_append( constT&); }; template< classT, classA_> Vec<T,A_>&Vec<T,A_>:: operator=( constVec<T,A_>&rhs) { //checkforself-assigment if(&rhs!= this) { uncreate(); create(rhs.begin(),rhs.end()); } return* this; } template< classT, classA_> voidVec<T,A_>::create() { data=avail=limit= 0; } template< classT, classA_> voidVec<T,A_>::create(size_typen, constT&val) { data=alloc.allocate(n); limit=avail=data+n; std::uninitialized_fill(data,limit,val); } template< classT, classA_> voidVec<T,A_>::create(const_iteratori,const_iteratorj) { data=alloc.allocate(j-i); limit=avail=std::uninitialized_copy(i,j,data); /*returnapointerto(onepast)thelastelementthatitinitialized*/ } template< classT, classA_> voidVec<T,A_>::uncreate() { if(data) { //destroy(inreverseorder)theelementsthatwereconstructed iteratorit=avail; while(it!=data) //destoryrunsT'sdestructorforthatobject,renderingthestorageuninitializedagain alloc.destroy(--it); alloc.deallocate(data,limit-data); } //resetpointerstoindicatethatVecisemptyagain data=limit=avail= 0; } template< classT, classA_> voidVec<T,A_>::grow() { //whengrowing,allocatetwiceasmuchspaceascurrentlyinuse size_typenew_size=std::max( 2*(limit-data),ptrdiff_t( 1)); //allocatenewspaceandcopyelementstothenewspace iteratornew_data=alloc.allocate(new_size); iteratornew_avail=std::uninitialized_copy(data,avail,new_data); //returntheoldspace uncreate(); //resetpointerstopointtothenewlyallocatedspace data=new_data; avail=new_avail; limit=data+new_size; } template< classT, classA_> //errorC4519:仅允许在类模板上使用默认模板参数 voidVec<T,A_>::unchecked_append( constT&val) { alloc.construct(avail++,val); } |
先介绍一下用到的一些类和函数:
allocator 模板类:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br></nobr>
|
#include<memory>
template< classT> classallocator { public: T*allocate(size_t); voiddeallocate(T*,size_t); voidconstruct(T*,size_t); voiddestroy(T*); //....... }; |
当然实际的接口没实现没那么简单,但大概实现的功能差不多:
allocate 调用operator new ;deallocate 调用 operator delete; construct 调用placement new (即在分配好的内
存上调用拷贝构造函数),destroy 调用析构函数。
两个std函数:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br></nobr>
|
template<
classIn,
classFor>
Foruninitialized_copy(In,In,For); template< classFor, classT> voiduninitialized_fill(For,For, constT&); |
如std::uninitialized_copy(i,j,data); 即将i ~ j 指向区间的数值都拷贝到data 指向的区间,返回的是最后一个初始化值的下一个位置。
std::uninitialized_fill(data,limit,val); 即将 data ~ limit 指向的区间都初始化为val 。
为了理解push_back 的工作原理,写个小程序测试一下:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br> 18<br> 19<br> 20<br> 21<br> 22<br> 23<br> 24<br> 25<br> 26<br> 27<br> 28<br> 29<br> 30<br> 31<br> 32<br> 33<br> 34<br></nobr>
|
#include<iostream>
#include "Vec.h" using namespacestd; classTest { public: Test() { cout<< "Test..."<<endl; } Test( constTest&other) { cout<< "copyTest..."<<endl; } ~Test() { cout<< "~Test..."<<endl; } }; intmain( void) { vector<Test>v2; Testt1; Testt2; Testt3; 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 的计算,如下的测试程序:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br> 18<br> 19<br> 20<br> 21<br> 22<br> 23<br> 24<br> 25<br></nobr>
|
#include<iostream>
#include "Vec.h" using namespacestd; intmain( 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 的源码就清楚了:
<nobr>1<br> 2<br> 3<br> 4<br> 5<br> 6<br> 7<br> 8<br> 9<br> 10<br> 11<br> 12<br> 13<br> 14<br> 15<br> 16<br> 17<br> 18<br> 19<br> 20<br></nobr>
|
void_Insert_n(const_iterator_Where,
size_type_Count, const_Ty&_Val) { //insert_Count*_Valat_Where ..... size_type_Capacity=capacity(); ..... else if(_Capacity<size()+_Count) { //notenoughroom,reallocate _Capacity=max_size()-_Capacity/ 2<_Capacity ? 0:_Capacity+_Capacity/ 2; //trytogrowby50% 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++编程规范