- 浏览: 92152 次
- 性别:
- 来自: 武汉
-
文章分类
最新评论
-
林奇峰:
嗯,都是这么设计的,这是scope问题,程序是为了处理数据,为 ...
context和getApplicationContext()介绍
从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
首先,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 } typedeftypename_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:dameng34@163.com >CreatedTime:Thu07Feb201306:51:12PMCST ************************************************************************/ #include<iostream> #include<cstddef> #include<memory> #include<algorithm> template<classT,classA_=std::allocator<T>> classVec { public://interface typedefT*iterator; typedefconstT*const_iterator; typedefsize_tsize_type; typedefTvalue_type; typedefstd::ptrdiff_tdifference_type; typedefT&reference; typedefconstT&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" usingnamespacestd; 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); return0; } |
从输出可以看出,构造函数调用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" usingnamespacestd; 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(); ..... elseif(_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++编程规范
相关推荐
C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 C++实战篇:STL-容器 ...
C++ STL(Standard Template Library,标准模板库)是C++编程语言中不可或缺的一部分,它提供了一组高效且灵活的算法、容器和迭代器。在STL中,`vector`是一种非常重要的容器,它是一个动态数组,允许在任意位置进行...
C++实现STL容器之Vector
在标题和描述中提到的"基于stl共享内存,可以像使用STL容器一样使用共享内存",指的是通过设计一个自定义的内存分配器(Allocator),使得STL容器如vector、list、map等能够在共享内存上进行操作。这种方式的优势...
在C++中,每个STL容器如`vector`、`list`、`map`等都有自己的分配器,负责内存的申请和释放。 **STL分配器(Allocator)的作用:** 1. **内存管理**:分配器的主要职责是为容器中的元素分配和释放内存。它定义了...
C++中的模板类`std::vector`是标准模板库(STL)的一部分,它提供了一个动态数组的功能,允许程序员在运行时改变数组的大小。`vector`类在内存管理上非常高效,它会自动处理内存分配和释放,使得在处理大量数据时...
- **allocator_type**:分配器类型,用于管理容器内存,默认为`std::allocator<value_type>`。 - **reference** 和 **const_reference**:分别表示对元素的引用和常量引用。 - **pointer** 和 **const_pointer**:...
4.4.6 内存分配器(Allocator) 56 4.4.7 概念(Concept)和模型(Model) 56 4.5 C++ STL存在的一些问题 57 4.6 本章小结 57 第5章 C++ STL泛化技术分析 58 5.1 算法和迭代器 58 5.1.1 算法 58 5.1.2 迭代...
STL包括六个主要组件:容器、算法、迭代器、函数对象(也称为仿函数)、内存分配器和适配器。这个压缩包文件中的内容可能涉及了这些组件中的“容器”,尤其是“vector”容器的使用和实践案例。 首先,我们来详细...
- **std::allocator类**:具体实现了上述需求集,并且可以用于STL容器中,如`std::vector`, `std::list`等。 #### 四、何时不使用Allocator 尽管`Allocator`提供了强大的功能,但在大多数情况下,开发者并不需要...
在C++编程语言中,STL(Standard Template Library,标准模板库)是不可或缺的一部分,它提供了许多高效且灵活的数据结构和算法。其中,`std::vector`是STL中的一个核心容器,它允许程序员像操作数组一样操作动态...
- 容器的容量管理和内存分配策略,如vector的reserve和capacity。 - set和map的插入、删除操作的时间复杂度分析。 - 模板元编程在STL中的应用,如迭代器的类型推断。 - 使用allocator定制内存管理。 - 介绍特殊...
4. **标准模板库(STL)**:STL是C++的一个强大工具,包含容器(如vector、list、map)、算法(如排序、查找)和迭代器等,大大提高了代码的可读性和效率。 5. **模板**:C++的模板功能允许创建泛型代码,可以用于...
总之,C++模板和STL库是现代C++编程的基础,它们提供了强大的抽象能力和高效的实现,使得程序员可以更专注于解决问题而不是底层细节。熟练掌握这两者,对于提升C++编程水平至关重要。在实际项目中,合理利用模板和...
一个小型 STL 模板库旨在提供简化的标准模板库(STL)实现,专注于...分配器(Allocator):实现简单的分配器,用于管理动态内存分配和释放。分配器支持自定义内存管理策略,如分块分配、内存池等,适合内存有限的嵌入
3. 预算器:如allocator(分配器),负责在内存中分配和释放对象。不同的容器可以使用不同的预算器,以适应不同内存管理策略。 4. 算法:STL提供了一系列的通用算法,如排序(sort)、查找(find)、拷贝(copy)、...
它为程序员提供了诸多数据结构的实现,如容器(container)、迭代器(iterator)和算法(algorithm)等,这些组件通过模板类和模板函数实现,以提供高度的代码重用性。STL最初由惠普实验室开发,后被集成进C++标准库中,...
STL(Standard Template Library,标准模板库)是C++中的一个重要组成部分,它提供了多种容器(如数组、向量、列表)、算法(如排序、搜索)以及迭代器等工具,极大地提高了代码的可读性和效率。 一、C++基础 C++的...
### C++ STL 学习经典知识点详解 #### 一、STL概述 C++ Standard Template Library (STL) 是 C++ 标准库的一部分,它提供了一组强大的工具,简化了程序员开发过程中对于数据结构和算法的操作。STL 的设计哲学强调了...