《STL源码剖析》笔记-vector

上一篇:《STL源码剖析》笔记-容器的分类

vector的元素排列和操作方式与array很相似,不同的是vector是动态空间,能够随着元素的添加自动扩展空间。它的实现关键在于对大小的控制以及重新配置空间时移动元素的效率。

vector的迭代器

// alloc是SGI STL的默认空间配置器
template <class T, class Alloc=alloc>
class vector
{
public:
   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;
 ...
 }

以上是vector的部分定义,可以看到它的iterator定义是value_type*,也就是普通的指针。因为vector维护的空间是一个连续线性空间,所以普通的指针就能满足vector迭代器的所有需求,例如*/->/++/–等。由此可见vector提供的是Random Access iterator。

vector的数据结构

template T,class Alloc=alloc>
class vector
{
...
protected:
     iterator start;          // 目前使用的空间头
     iterator finish;         // 目前使用的空间尾
     iterator end_of_storage; // 目前可用的空间尾
...
}

vector采用的数据结构很简单,就是连续线性空间。以上定义中迭代器start和finish之间是已经被使用的空间范围,end_of_storage是整块连续空间包括备用空间的尾部。end_of_storage存在的原因是为了降低空间配置的成本,vector实际分配空间大小的时候会比客端需求的大一些,以便于未来的可能的扩展。

运用这三个迭代器成员,就能提供begin、end、size等函数:

template T,class Alloc=alloc>
class vector
{
...
pubic:
     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() { return begin() = end(); }
     reference front { return *begin(); }
     reference operator[](size_type n) { return *(begin() + n); }
     reference front() { return *begin(); }
     reference back() { return *end() - 1; }     
...
}

这里写图片描述

vectord的构造与内存管理

vector提供了很多的构造函数,以下例举一些:

template <class T, class Alloc = alloc>  
class vector {
public:
    // 默认构造空的vector
    vector() : start(0), finish(0), end_of_storage(0) {}
    // 构造大小为n,初始值为都value的vector
    vector(size_type n, 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); }
    // 不允许隐式转换,构造大小为n,初始值为都元素默认初始值的vector
    explicit vector(size_type n) { fill_initialize(n, T()); }
    // 复制构造函数
    vector(const vector& x) {
        start = allocate_and_copy(x.end() - x.begin(), x.begin(), x.end());
        finish = start + (x.end() - x.begin());
        end_of_storage = finish;
    }

protected:
    // 使用了simple_alloc,每次配置一个元素大小
    typedef simple_alloc data_allocator;

    void fill_initialize(size_type n, const T& value) {
        start = allocate_and_fill(n, value);
        finish = start + n;
        end_of_storage = finish;
    }
    // 配置空间并复制内容
    iterator allocate_and_copy(size_type n,
                             const_iterator first, const_iterator last) {
        iterator result = data_allocator::allocate(n);  // 分配空间
        uninitialized_copy(first, last, result);  // 将迭代器[first,last)之间的内容拷贝到result开始的空间
        return result;
    }
    // 配置空间并填满内容
    iterator allocate_and_fill(size_type n, const T& x) {
        iterator result = data_allocator::allocate(n);
        uninitialized_fill_n(result, n, x);
        return result;
    }
};

vector的定义默认使用的是alloc作为空间配置器,并且为了方便以元素大小为配置单位定义了data_allocator。关于alloc和simple_alloc详见《STL源码剖析》笔记-空间配置器。data_allocator::allocate(n)会分配n个元素空间大小,uninitialized_fill_n则会根据迭代器的型别特性(详见《STL源码剖析》笔记-迭代器iterators)选择效率较高的方式进行构造。

当使用push_back()将元素插入尾部时,会先检查是否有备用空间,如果有就直接在备用空间中构造,并调整迭代器finish。否则,就需要进行扩展空间:

template <class T, class Alloc = alloc>
void vector::push_back(const T& x)
{
    if (finish != end_of_storage) {  
        // 还有备用空间,直接构造
        construct(finish, x);
        ++finish;
    }
    else
        insert_aux(finish, x);  // 见后续代码
}

// 在某个位置插入元素
template <class T, class Alloc = alloc>
void  vector::insert_aux(iterator position, const T& x)
{
    if (finish != end_of_storage) {
        // 还有备用空间
        // 将最后一位元素构造给备用空间中的第一位
        construct(finish, *(finish - 1));
        ++finish;

        // 把[position,finish - 2]的元素后移一位
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);  // 见后续代码
        *position = x_copy;
    }
    else {
        // 没有备用空间,需要扩展,扩展方式为抛原有空间重新分配
        // 原则为:原大小为0,则配置1个;否则,配置原有的两倍。为什么是两倍,详见https://blog.csdn.net/yangshiziping/article/details/52550291
        const size_type old_size = size();
        const size_type new_size = old_size == 0 ? 1 : 2 * old_size;

        iterator new_start = data_allocator::allocate(new_size);
        iterator new_finish = new_start;

        try
        {
            // 将原有的position前的内容拷贝到新的空间
            // 赋值position位置为x
            // 将position后的内容拷贝到新的空间
            new_finish = uninitialized_copy(start, position, new_start);
            construct(new_finish, x);
            ++new_finish;
            new_finish = uninitialized_copy(position, finish, new_finish);  // 此处原作者有所误解,并不是将备用空间拷贝
        }
        catch (...)
        {
            destroy(new_start, new_finish);
            data_allocator::deallocate(new_start, new_size);
            throw;
        }

        // 析构并释放原有空间
        destroy(begin(), end());
        deallocate();

        // 调整迭代器位置指向新的空间
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + new_size;
    }
}

// 将[first,last]按倒序赋值给[first - (result-last), last - (result-last)]
// (result-last)大于0时,相当于将元素[first,last]后移(result-last)位
// (result-last)小于0时,相当于将元素[first,last]前移(result-last)位
template<class BidirectionalIterator1, class BidirectionalIterator2>
BidirectionalIterator2 copy_backward ( BidirectionalIterator1 first,
                                       BidirectionalIterator1 last,
                                       BidirectionalIterator2 result )
{
    while (last != first) *(--result) = *(--last);
    return result;
}

以上代码可以看到,vector扩展空间的方式是申请新的空间,然后把原有的内容复制过来,然后释放原有空间,这是为了保证空间的连续性。因此,一旦发生了空间的扩展,原有的迭代器全都会失效,这点需要特别注意。另外,我们看一下空间扩展的测试代码:

#include 
#include 
using namespace std;

int main(int argc, char* argv[])
{
    vector<int> vec;
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <0);
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <0);
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <0);
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <0);
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <0);
    cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <cout << "size()=" << vec.size() << endl;
    cout <<"capacity()=" << vec.capacity() <return 0;
}

// 输出结果
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()=4
capacity()=8
size()=3
capacity()=8
size()=0
capacity()=8

vector的元素操作

vector提供的元素操作函数很多,下面只举例常用的几个。

// 移除最后一个元素
void pop_back()
{
    --finish;
    destroy(finish);
}

// 移除指定迭代器的元素
iterator erase(iterator position)
{
    if (position + 1 != end())
        copy(position + 1, finish, position);

    --finish;
    destroy(finish);
    return position;
}

// 移除[first,last)之间的元素
iterator erase(iterator first, iterator last)
{
    iterator i = copy(last, finish, first);
    // 析构掉需要析构的元素
    destroy(i, finish);
    finish = finish - (last - first);
    return first;
}

// 清空
void clear()
{
    erase(begin(), end());
}

// 在指定位置插入n个x
template <class T, class Alloc>
void insert(iterator position, size_type n, const T& x)
{
    if (n != 0)
    {
        if (size_type(end_of_storage - finish) >= n)
        {      
            // 备用空间大于等于“新增元素个数”
            T x_copy = x;
            // 以下计算插入点后面的现有元素个数
            const size_type elems_after = finish - position;
            iterator old_finish = finish;
            if (elems_after > n)
            {
                // 插入点后面的现有元素个数大于新增元素个数的情况
                uninitialized_copy(finish - n, finish, finish);
                finish += n;    
                // 从position开始向后移n位
                copy_backward(position, old_finish - n, old_finish);
                fill(position, position + n, x_copy); // 从插入点开始填入新值
            }
            else
            {
                // 插入点后面的现有元素个数小于等于新增元素个数的情况
                // 无法和大于的情况一样使用copy_backward(position, old_finish - n, old_finish),position会大于old_finish - n
                uninitialized_fill_n(finish, n - elems_after, x_copy);
                finish += n - elems_after;
                uninitialized_copy(position, old_finish, finish);
                finish += elems_after;
                fill(position, old_finish, x_copy);
            }
        }
        else
        {
            // 备用空间小于“新增元素个数”(那就必须配置额外的内存)
            // 首先决定新长度:旧长度的两倍,或旧长度+新增元素个数
            const size_type old_size = size();
            const size_type len = old_size + max(old_size, n);
            // 以下配置新的vector空间
            iterator new_start = data_allocator::allocate(len);
            iterator new_finish = new_start;
            __STL_TRY
            {
                // 以下首先将旧的vector的插入点之前的元素复制到新空间
                new_finish = uninitialized_copy(start, position, new_start);
                // 以下再将新增元素(初值皆为n)填入新空间
                new_finish = uninitialized_fill_n(new_finish, n, x);
                // 以下再将旧vector的插入点之后的元素复制到新空间
                new_finish = uninitialized_copy(position, finish, new_finish);
            }
#ifdef  __STL_USE_EXCEPTIONS
            catch(...)
            {
                destroy(new_start, new_finish);
                data_allocator::deallocate(new_start, len);
                throw;
            }
#endif /* __STL_USE_EXCEPTIONS */

            destroy(start, finish);
            deallocate();
            start = new_start;
            finish = new_finish;
            end_of_storage = new_start + len;
        }
    }
}

下一篇:《STL源码剖析》笔记-list

你可能感兴趣的:(C-C++,STL源码剖析)