一、vector概览
上一篇博客中我们介绍了string的简化实现,今天我们来介绍另一个STL当中比较重要的容器——vector。
private 字段:数据结构
在我们的实现中,vector作为通用容器需要兼容不同的数据/对象类型,所以我们需要用模板这个特性,为此,我们涉及data_字段为T*类型,其中T是模板名,内存中为数组所分配的内存长度是capacity_,实际长度是size_。
public 方法:对外接口
对外接口的实现上,重点考虑push_back()和pop_back()。的实现需要考虑左右值的兼容,为此,我们用到了完美转发、万能引用,还需要考虑扩容的逻辑。pop_back方法的实现比较简单,就是要记得调用容器中对象的析构函数。
Rule of five
我们的vector实现有对堆上的资源进行管理,所以需要rule of five,但是只实现析构函数;由于只使用index找值,且只调用push_back和pop_back,所以拷贝构造、移动构造、拷贝赋值、移动赋值可以都禁止掉。
二、vector实现
我们使用默认构造和默认成员初始化的办法,这样避免过度复杂化实现:
classvector{public:vector()=default;private:T*data_=nullptr;size_t size_=0;size_t capacity_=0;};现在考虑实现析构函数,析构函数的作用有两个:1.逐个调用容器中对象的析构函数,清空容器;2.释放已分配的内存。这里注意clear()方法是STL vector的公有方法,所以单独拿出来。释放内存的实现使用operator detele,因为之前clear方法已经调用了析构函数了。
~vector(){clear();//清空容器deallocate();//释放内存}voidclear(){for(size_t i=0;i<size_;i++){data_[i].~T();}size_=0;}private:voiddeallocate(){::operatordelete(data_);data_=nullptr;capacity_=0;}这里顺便实现一下allocate():注意我们的vector实现中构造与内存分配总是采取分离的策略,所以也使用operator new。operator new返回的类型是void*,所以需要类型转换
T&allocate(){returnstatic_cast<T*>(::operatornew(capacity_*sizeof(T)));}现在重头戏来了,让我们来实现push_back()方法。
push_back要考虑到左右值两种的情况。我们这里使用emplace_back作为统一的入口,用变参模板+完美转发+万能引用保留参数数目和值类别;emplace_back:在扩容后使用placement new原地构造对象。
voidpush_back(constT&value){emplace_back(value);}voidpush_back(T&&value){emplace_back(std::move(value));}template<typename...Args>//变参模板T&emplace_back(Args&&...args){//万能引用if(size_==capacity_){reallocate(capacity_==0?1:capacity_*2);//扩容}new(data_+size_)T(std::forward<Args>(args)...);//placement newreturndata_[size_++];//提前位移,为下一个emplace对象腾位置}现在来实现reallocate这个函数:
- 分配新内存
- 复制数据到新内存(placement new)
- 如果抛异常需要清空新内存的数据
- 清空原内存数据,释放内存
- 设置新的cap、sz,data指向新内存
voidreallocate(size_t new_cap){T*new_data=allocate(new_cap);//分配新内存size_t new_size=0;try{for(;new_size<size_;new_size++){//placement new + 移动优化new(new_data+new_size)T(std::move_if_noexcept(data_[new_size]));}}catch(...){//中间抛出异常时清空、释放for(size_t i=0;i<new_size;i++){new_data[i].~T();}::operatordelete(new_data(i));throw;//rethrow,向上传播异常}clear();deallocate();size_=new_size;capacity_=new_cap;data_=new_data;}接下来实现pop_back(),popback比较简单,就是将最后一个元素从data中删去,调析构、size–即可。我们顺便实现back方法,pop_back一般会配合back使用。
T&back(){returndata_[size_-1];}constT&back()const{returndata_[size_-1];}//const对象的情况要特别处理voidpop_back(){if(size_>0){data_(--size_).~T();}}现在只剩下一些比较简单的方法还没实现:
T&operator[](size_t index)noexcept{returndata_[index];}constT&operator[](size_t index)constnoexcept{returndata_[index];}size_tsize(){returnsize_;}size_tcapacity(){returncapacity_;}