`
aspnetwinform
  • 浏览: 92152 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
社区版块
存档分类
最新评论

从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)

 
阅读更多

首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下:

C++ Code
<nobr>1<br> 2<br></nobr>
template<class_Ty,class_Ax=allocator<_Ty>>
classvector;

但在VC2008 中vector 还有基类,如下:

C++ Code
<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:

C++ Code
<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模板类:

C++ Code
<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:


C++ Code
<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 模板类:

C++ Code
<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函数:

C++ Code
<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 的工作原理,写个小程序测试一下:


C++ Code
<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 的计算,如下的测试程序:


C++ Code
<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 的源码就清楚了:

C++ Code
<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-容器 ...

    C++ STL vector 容器介绍

    C++ STL(Standard Template Library,标准模板库)是C++编程语言中不可或缺的一部分,它提供了一组高效且灵活的算法、容器和迭代器。在STL中,`vector`是一种非常重要的容器,它是一个动态数组,允许在任意位置进行...

    C++实现STL容器之Vector

    C++实现STL容器之Vector

    基于stl共享内存,可以像使用STL容器一样使用共享内存

    在标题和描述中提到的"基于stl共享内存,可以像使用STL容器一样使用共享内存",指的是通过设计一个自定义的内存分配器(Allocator),使得STL容器如vector、list、map等能够在共享内存上进行操作。这种方式的优势...

    stl allocator(stl 分配器)

    在C++中,每个STL容器如`vector`、`list`、`map`等都有自己的分配器,负责内存的申请和释放。 **STL分配器(Allocator)的作用:** 1. **内存管理**:分配器的主要职责是为容器中的元素分配和释放内存。它定义了...

    C++ 模板类 vector

    C++中的模板类`std::vector`是标准模板库(STL)的一部分,它提供了一个动态数组的功能,允许程序员在运行时改变数组的大小。`vector`类在内存管理上非常高效,它会自动处理内存分配和释放,使得在处理大量数据时...

    C++_STL之set容器使用方法

    - **allocator_type**:分配器类型,用于管理容器内存,默认为`std::allocator&lt;value_type&gt;`。 - **reference** 和 **const_reference**:分别表示对元素的引用和常量引用。 - **pointer** 和 **const_pointer**:...

    C++ STL开发技术导引(第5章)

    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 迭代...

    C/C++ 学习入门代码案例 - STL六大组件:容器、算法、迭代器、内存分配器、适配器实例

    STL包括六个主要组件:容器、算法、迭代器、函数对象(也称为仿函数)、内存分配器和适配器。这个压缩包文件中的内容可能涉及了这些组件中的“容器”,尤其是“vector”容器的使用和实践案例。 首先,我们来详细...

    彻底学习STL中的Allocator

    - **std::allocator类**:具体实现了上述需求集,并且可以用于STL容器中,如`std::vector`, `std::list`等。 #### 四、何时不使用Allocator 尽管`Allocator`提供了强大的功能,但在大多数情况下,开发者并不需要...

    心希盼 c++ STL Vector

    在C++编程语言中,STL(Standard Template Library,标准模板库)是不可或缺的一部分,它提供了许多高效且灵活的数据结构和算法。其中,`std::vector`是STL中的一个核心容器,它允许程序员像操作数组一样操作动态...

    C++ STL程序员面试题

    - 容器的容量管理和内存分配策略,如vector的reserve和capacity。 - set和map的插入、删除操作的时间复杂度分析。 - 模板元编程在STL中的应用,如迭代器的类型推断。 - 使用allocator定制内存管理。 - 介绍特殊...

    c++(程序设计)从零开始

    4. **标准模板库(STL)**:STL是C++的一个强大工具,包含容器(如vector、list、map)、算法(如排序、查找)和迭代器等,大大提高了代码的可读性和效率。 5. **模板**:C++的模板功能允许创建泛型代码,可以用于...

    C++模板与STL库介绍

    总之,C++模板和STL库是现代C++编程的基础,它们提供了强大的抽象能力和高效的实现,使得程序员可以更专注于解决问题而不是底层细节。熟练掌握这两者,对于提升C++编程水平至关重要。在实际项目中,合理利用模板和...

    小型STL模板库,适用于学习C++的初学者,用于掌握C++基础

    一个小型 STL 模板库旨在提供简化的标准模板库(STL)实现,专注于...分配器(Allocator):实现简单的分配器,用于管理动态内存分配和释放。分配器支持自定义内存管理策略,如分块分配、内存池等,适合内存有限的嵌入

    C++高手之STL学习资料

    3. 预算器:如allocator(分配器),负责在内存中分配和释放对象。不同的容器可以使用不同的预算器,以适应不同内存管理策略。 4. 算法:STL提供了一系列的通用算法,如排序(sort)、查找(find)、拷贝(copy)、...

    C++《STL》讲义.pdf

    它为程序员提供了诸多数据结构的实现,如容器(container)、迭代器(iterator)和算法(algorithm)等,这些组件通过模板类和模板函数实现,以提供高度的代码重用性。STL最初由惠普实验室开发,后被集成进C++标准库中,...

    C++&STL快速入门(7个简明教程)

    STL(Standard Template Library,标准模板库)是C++中的一个重要组成部分,它提供了多种容器(如数组、向量、列表)、算法(如排序、搜索)以及迭代器等工具,极大地提高了代码的可读性和效率。 一、C++基础 C++的...

    C++STL学习经典

    ### C++ STL 学习经典知识点详解 #### 一、STL概述 C++ Standard Template Library (STL) 是 C++ 标准库的一部分,它提供了一组强大的工具,简化了程序员开发过程中对于数据结构和算法的操作。STL 的设计哲学强调了...

Global site tag (gtag.js) - Google Analytics