C++深入学习:STL源码剖析 (2) 从源码深入剖析vector

C++深入学习:STL源码剖析 (2) 从源码深入剖析vector

​ vector的使用就像数组,但是vector的空间是可变的。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。vector的实现技术,关键就在于对大小的控制以及重新配置时的数据移动效率。需要注意的是,对于空间的扩充,是(配置新空间->数据移动->释放旧空间)的大工程,时间成本是较高的,因此如果每次需要扩充仅仅只扩充所需的,则会导致频繁的扩充,因此vector采用了未雨绸缪的考虑。

学习vector的核心在于理解 finish和end_of_storage的区别, 即size和capacity的区别,还有如何进行的空间扩充机制,同时,insert函数也处处都是细节,非常值得学习

vector的核心class定义

vector的class定义: (重要和复杂的函数之后详细展开) (诸多思路都在注释中)

template<class T,class Alloc=alloc>
class vector{
public:
    //用typedef定义一些变量
    typedef T value_type;
    typedef value_type* pointer;  //指针
    typedef value_type* iterator; //迭代器
    typedef value_type& reference; 
    typedef size_t      size_type;
    typedef ptrdiff_t difference_type;
protected:
    typedef simple_alloc<value_type,Alloc> data_allocator;//空间配置器
    /*----------vector的三个核心变量------------------------------*/
    iterator start;   //目前使用容器的头
    iterator finish;   //目前使用空间的尾
    iterator end_of_storage;  //目前可用空间的尾
    /*-------------注意区分finish和end_of_storage-----------------*/
    
    void insert_aux(iterator position,const T&x);
    void deallocate();
    void fill_initialize(size_type n,const T&value);
    iterator allocate_and_fill(size_type n,const T&x){
        iterator result=data_allocator::allocate(n);
        uninitialized_fill_n(result,n,x);
        return result;
    }
public:
    iterator begin() {return start;} //返回容器头
    iterator end()   {return finish;} //返回容器尾
    size_type size() const {return size_type(end()-begin());} //返回容器已用大小
    size_type capacity() const {return size_type(end_of_storage-begin());}//返回容器真实占用空间大小
    bool empty() const {return begin()==end();} //判断是否为空
    reference operator[](size_type n) {return *(begin()+n);} //重载[]
    reference front(){return *begin();}
    reference back(){return *(end()-1);}
    
    /*-------构造函数----------------*/
    vector():start(0),finish(0),end_of_storage(0){} 
    vector(size_type t,const T&value){fill_initialize(n,value);}
    vector(int n,const T&value){fill_initialize(n,value);}
    vector(long n,const T&value){fill_initialize(n,value);}
    explicit vector(size_type n){fill_initialize(n,T());}
    //析构函数
    ~vector(){
        destroy(start,finish);   //先析构
        deallocate();  //再释放空间
    }
    
    //push pop   //插入和释放元素
    void push_back(const T& x);
    void pop_back();
    iterator erase(iterator position);  //消除某个位置上的元素
    
    //重新规划大小
    void resize(size_type new_size,const T& x);  //非常重要
    void resize(size_type new_size){resize(new_size,T());}
    void clear(){erase(begin(),end());}  //消除空间
    
}
vector的数据结构

vector维护的是一个连续线性空间,所有不论其元素类型为何,普通指针都可以作为vector的迭代器而满足所有必要条件。vector支持随机存储,因此vector提供的是 Random Access Iterators

vector为了维护连续的线性空间,以两个迭代器start何finish分别指向配置得来的连续空间目前已经被使用的范围,并以迭代器end_of_storage指向整个连续空间(含备用空间)的尾端。为了避免频繁的空间扩充导致性能下降,vector实际的配置大小可能比客户端需求量要更大一些,以备将来的扩充,也就是 capacity>=size;一个vector的容量永远大于或等于其大小,一旦容量等于大小,便满载了,之后有新增元素就必须重新resize。

通过begin,finish,end_of_storage三个变量,便可以达到提供首位的标识、vector的大小、容量、空容器判断等诸多机制。

下面这段很简单代码说明vector的size和capacity的不同: (g++编译器)

    vector<int> v;
    cout << "size:" << v.size() << ",capacity:" << v.capacity() << endl;
    for (int i = 0; i < 10;i++)
    {
        v.push_back(i);
        cout << "size:" << v.size() << ",capacity:" << v.capacity() << endl;
    }
/*------------------------
size:0,capacity:0
size:1,capacity:1
size:2,capacity:2
size:3,capacity:4
size:4,capacity:4
size:5,capacity:8
size:6,capacity:8
size:7,capacity:8
size:8,capacity:8
size:9,capacity:16
size:10,capacity:16
----------------------*/
vector的核心函数实现机制

tips:为了简洁在下面代码中都没有加类限定符

resize函数
void resize(size_type new_size,const T& x)
{
	if(new_size<size())  //要变的尺寸更小,则把之后多余的消去
        erase(begin()+new_size,end());
    else
        insert(end(),new_size-size(),x);  //用x填充之后增加的数据   
}
push_back函数

当向vector尾部插入元素时,首先检查是否还有备用空间,如果有备用空间则直接在尾部构造元素即可,并调整迭代器finish位置;如果没有备用空间,则需要扩充空间(重新配置、移动数据、释放原空间)

void push_back(const T& x)
{
    if(finish!=end_of_storage){
        construct(finish,x); //在finish处赋值为x
        finish++; //调整finish位置
    }
    else
    {
    	insert_aux(end(),x); //扩充空间并插入元素
    }
}
inser_aux函数

insert_aux函数是protected类型成员,是vector的push_back实现的基础,即动态扩容的基础。

动态增加大小,并不是在原空间之后接续新的空间,因为无法保证原空间之后尚有可配置的空间,而是以原大小的两倍另外配置一块较大的空间,然后将内容拷贝过来,在新空间上原内容之后构造新元素,释放原空间。

因此,注意:对vector的任何操作,一旦引起空间重新配置,则指向原vector的所有指针迭代器都失效

void insert_aux(iterator position,const T&x)
{
    if(finish!=end_of_storage) //后面还有备用空间 
    {
        //则在备用空间出构造一个元素,以vector的最后一个元素作为初值初始化该新位置的元素值
        construct(finish,*(finish-1));  
        ++finish; //调整finish迭代器
        //将position到最后的元素向后移动,为position处腾出位置,然后将position处值置为x
        T x_copy=x;
        copy_backward(position,finish-2,finish-1);
        *position=x_copy;
    }
    else  //后面没备用空间,需要扩充
    {
        //首先需要确定之后申请空间的大小
        //g++申请原则:如果原大小为0,则配置1个元素大小的空间
        //如果原大小不为0,则配置2*原大小的空间
        //前半段放原数据,后半段放置新数据,多余的空余
        const size_type old_size=size();
        const size_type len=old_size!=0?2*old_size:1;
        //申请新的空间
        iterator new_start=data_allocator::allocate(len); //实际配置
        iterator new_finish=new_start;
        try{
            //1.将position之前的值拷贝过来
            new_finish=uninitialized_copy(start,position,new_start);
            //在position处设置新值
            construct(new_finish,x);
            ++new_finish; //设置新finish
            //将position之后的值拷贝过来
            new_finish=uninitialized_copy(position,finish,new_finish);
        }
        catch(...){  //如果出现错误,则rollback
            		 //因此扩容过程就像数据库中常说的 事件操作,即要么全部完成,要么全部撤回不做
            destroy(new_start,new_finish);
            data_allocator::deallocate(new_start,len);
            throw;
        }
        //析构并释放原来的vector
        destroy(begin(),end());
        deallocate();
        
        //调整迭代器指向新的vector对应位置
        start=new_start;
        finish=new_finish;
        end_of_storage=new_start+len;
    }
}
pop_back函数

pop_back函数不影响vector的capacity,只是size-1。pop_back的过程就是将尾部迭代器向前移动一格,然后将其之前占据的元素和空间释放

void pop_back(){
    --finish;        //将尾部迭代器向前移动
    destroy(finish);  //释放
}
erase函数

erase函数用来释放某一位置或某一区间的所有元素。

iterator erase(iterator first,iterator last)
{
    iterator i=copy(last,finish,first); //将后面的元素覆盖到以first开头的位置
    desrtoy(i,finish);
    finish=finish-(last-first);
    return first;
}
iterator erase(iterator position)
{
    if(position+1!=end()) //position不在末尾
    {
        copy(position+1,finish,position); //将position之后的元素覆盖至position为起始的位置
    }
    --finish;
    destroy(finish);
    return position;
}
insert函数

insert函数的实现处处都是细节,果然是大师!

insert函数目的是在position处插入n个x

//将n个元素x插到position位置之前。
//STL规定,区间描述时一律左闭右开(半开半闭),[start, finish)
template<class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
	if(n != 0)	//元素个数大于0才需要进行操作。
	{
        当前空间可容纳新元素
		if(size_type(end_of_storage - finish) >= n)  
		{
			T x_copy = x;
			const size_type elems_after = finish - position;//计算插入点之后的元素个数。
			iterator old_finish = finish;	//记下原先区间内末尾元素的下一位置。
			//插入点之后现有元素个数 大于新增元素个数(n)
            if( elems_after > n)	
			{
				//将原区间[finish-n, finish)填充到目标区间[finish, finish+n)。
				uninitialized_copy(finish-n, finish, finish);	
				finish += n;	//尾端标记后移。
	//将源区间[position,old_finish-n)从逆向填充到目标区间[old_finish-n, old_finish)。
				copy_backward(position, old_finish-n, old_finish);	
				//将目标区间[position, position+n)填充成x_copy。
				fill(position, position+n, x_copy);
			}
			else	//插入点之后现有元素个数 小于等于 新增元素个数(n)。
			{
				//将[finish, finish+(n-elems_after))全部填充成x_copy。
				uninitialized_fill_n(finish, n-elems_after, x_copy);
				finish += n- elems_after;	//尾端标记后移。
	//将原区间[position, old_finish)填到目标区间[finish, finish+(old_finish-position))
				uninitialized_copy(position, old_finish, finish);
				finish += elems_after;	//尾端标记后移。
				//将目标区间[position, old_finish)填充成x_copy。
				fill(position, old_finish, x_copy);
			}
		}
		else  //因为剩余空间不足,所以需要进行扩容
		{
			//扩容机制:旧长度的两倍或者旧长度+新增元素个数 ,取max
            const size_type old_size=size();
            const size_type len=old_size+max(old_size,n);
            //申请新空间
            iterator new_start=data_allpcator::allocate(len);
            iterator new_finish=new_start;
            __STL_TRY{
                //将旧的vector的position之前的元素拷贝至新空间
                //新增n个新元素
                //将旧的vector的position之后的元素拷贝至新空间
                new_finish=uninitialized_copy(start, position, new_start);
                new_finish=uninitialized_fill_n(new_finish,n,x);
                new_finish=uninitialized_copy(position, old_finish, new_finish);
            }
            #ifdef __STL_USE_EXCEPTIONS
            catch() //有异常则rollback,要么成功,要么不做
            {
                destroy(new_start,new_finish);
            	data_allocator::deallocate(new_start,len);
            	throw;
            }
            #endif
            
            //释放原来空间
            destroy(start,finish);
            deallocate();
            //调整迭代器位置
            start=new_start;
            finish=new_finish;
            end_of_storage=new_start+len;
		}
	
    }

}

猜你喜欢

转载自blog.csdn.net/dingdingdodo/article/details/106895925