STL源码分析之vector

C++标准模板库在日程编程应用非常的广泛,之前看到一篇大牛文章说,用C++开发,尽量用容器类+迭代器来代替数组+指针,因为数组+指针容易越界,或者内存泄露,相反,容器类和+迭代器都有国外大神将底层封装好,使用安全简单。而且标准模板库再一定程度上可以提高我们编程效率,假如要对一个结构体数组排序,写一个比较函数或者仿函数,调用sort函数即可。换做是c语言,还需自己写排序函数。。。

vector是有序容器里使用最广泛的容器。今天想分析下vector这个容器的实现,看这个文章之前,需要好好理解typetraits和iterator_traits,这两个是就是著名的特性萃取器,一个是萃取类型,一个是萃取迭代器类型。由于这两个东西很繁琐,就不写了,在代码中有用到地方,会有注释。

构造函数

先介绍vector这个模板类的成员变量:

640?wx_fmt=png

finish表示下一个存储数据的位置,也就是说vector数组是存储在[start,end)里面的,而end_of_storage是可用的空间的尾,也就是说在finish和end_of_storage之间还可以存储数据,如果有容量的话。

好接下来是构造函数,首先是默认构造函数:

640?wx_fmt=png

这四个函数都调用了fill_initialize(size_type n, const T& value) 这个函数,这个函数先调用allocate_and_fill这个函数分配内醋,并且填充数据。先看下fill_initialize函数:

640?wx_fmt=png


接来下看下allocate_and_fill这个函数:

640?wx_fmt=png


这个函数要说明几点:

n是类型大小内存单元个数,作为data_allocator::allocate(n)这个函数的参数,并不是就分配n个字节,因为在allocate函数内部是如下调用的:

640?wx_fmt=png



所以分配到的内存是n个sizeof(T)大小的内存。

  1. 这个函数uninitialized_fill_n是内处处理函数,在 stl_uninitialized.h这个文件中,这个文件还包含了好几个类似函数。

  2. 如果一块内存单元赋值错误,那么会删除所有内存,就是类似"commit and rollback"回滚的功能。

接下看下 uninitialized_fill_n这个函数:

640?wx_fmt=png


这个函数根据这个vector的数据类型,调用不同的函数:

640?wx_fmt=png


先判断这个数据类型是否是POD,POD是plain old data的简称,表示c语言的基础数据类型,int ,long,double等等,还有c语言的struct接口。如果是POD类型,直接用fill_n函数填充内存即可;如果不是POD类型,就需要调用构造函数来初始化内存,代码如下:

640?wx_fmt=png


这里的构造函数调用是placement new,代码如下:

640?wx_fmt=png


到此为止,构造函数已构造完毕,接下来看下复制构造函数

640?wx_fmt=png


先调用allocate_and_copy函数分配内存,以及复制数据,该函数如下;

640?wx_fmt=png


接下来,看下uninitialized_copy这个函数;

640?wx_fmt=png

根据值的类型,调用不同的函数。也是区分是否为POD,调用不同的复制函数,这里不再讲述。

析构函数

这里构造函数暂时讲这么多,接来下,看下析构函数,析构函数很简单,先析构[start,finidh),然后再把内存还给系统或内存池,来看下代码:

640?wx_fmt=png



destroy函数根据数据类型,如果是POD,则什么都不做,如果不是POD,则需要调用析构函数一个一个的析构。deallocate函数如下:

640?wx_fmt=png


这个函数回收[start,end_of_storage)之间的内存,如果是大于128b,则还给系统,如果小于128b,则还给内存池,后面可以继续使用。

一些常用函数

push_back函数

当创建一个空的vector时,此时空间为0,所以需要分配1个内存;当空间不够,且此时空间大小不为空时,分配原有空间的一倍,用于后续使用。来看下代码:

640?wx_fmt=png


来看下insert_aux()函数:

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

根据这个函数,可以得到在使用vector过程中一个非常重要的注意点。因为没增加一个元素时,都有可能重新分配内存,那么原先的迭代器就将失效了,因为指向的内存已经被析构了。所以vector有添加数据时,一定要重新定义迭代器。

一些小函数

640?wx_fmt=png

640?wx_fmt=png

这些小函数,最重要要区分size()和capacity()函数,前者是vector已有的元素数量,后者是这个vector总共可以存储的元素数量。

erase函数

这个函数有一个注意点,所以这里讲解下:

640?wx_fmt=png


这个函数需要注意的是,当删除一段元素时,size()大小是会变化的,但是capacity()是不变的。

其他函数就不一一介绍了。接下来总结下:

  1. 在看vector函数之前,一定要弄懂内存池,配置器和特性萃取,否则看不懂vector源码。

  2. 当向vector一个元素时,此时的迭代器是不安全的,需要重新定义。

  3. 当调用erase函数时,size()函数会变化,capacity不会花生变化。


  4. 640?wx_fmt=jpeg


猜你喜欢

转载自blog.csdn.net/t146lla128xx0x/article/details/80729894