《STL 源码剖析》 vector 实现原理

摘抄于 《STL 源码剖析》 4.2

vector概述

vector 和 array 非常相似。两者的唯一差别在于空间的运用的灵活性。

array是静态空间,一旦配置就不能改变。

如果要改变需要 用户自己操作:配置一个新空间将元素从旧地址搬到新地址,把原来的空间释放。

vector 是 动态控件,随着元素的加入,内部机制会自行扩充空间以容纳新元素。

vector的实现技术,关键 在于 其对大小的控制 以及 重新配置时的数据移动效率。

vector迭代器

因为 vector 维护的是一个连续线性空间,因此 普通指针可以作为vector的迭代器,满足迭代器的必要条件。

vector 提供的是 Random Access Iterators

Random Access Iterators 是迭代器的五大分类之一

vector 数据结构

class vector{
...
protected:
    iterator start; // 指向使用连续空间的第一个元素的位置
    iterator finish;// 指向使用连续空间的最后一个元素的下一个位置
    iterator end_of_storage;// 指向整个连续空间的尾端
...
};

通过 start,finish,end_of_storage 三个迭代器 可以很简单的提供大部分功能:大小、是否为空、[ ]、前端元素、后端元素:

class vector{
...
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 构造与内存管理

  1. 构造
// 指定大小和初值的构造函数
vector(size_type n,const T& value){ fill_initialize(n,value); }
 
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_fill(size_type n,const T& x)
{
    iterator result = data_allocator::allocate(n); // 分配 n个 元素的空间
    uinitialized_fill_n(result,n,x); // 给 [result,result+n) 设置初始值 x
    return result;
}
 
  1. push_back 原理
void push_back(const T& x)
{
    if(finish != end_of_storage) // 说明还有备用空间
    {
        construct(finish, x); // constuct(T* p, const T& x); 构造函数
        ++finish;
    }
    else
        insert_aux(end(), x);// vector 成员函数
}
 
// 实现 空间资源的重新分配,得到更大的空间
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
    if(finish != end_of_storage) // 还有备用空间 - 上面明明判断过了,这边加上去的理由应该是 防止其他情况 处理吧
    {
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    }
    else
    {
        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
        {
            // 将原本的内容拷贝 到新的 vector
            new_finish  = uninitialized_copy(start, position, new_start); // 将 [start,position) 的内容 拷贝 到 new_start为起点的 vector中
            // 为新元素 设定 初值 x
            construct(new_finish, x);
            ++new_finish;
            // 然后 再把 后半段 也拷过来
            new_finish  = uninitialized_copy(position, finish, new_finish); // 将 [position,finish) 的内容 拷贝 到 new_finish为起点的 vector中
        }
        catch(...)
        {
            // 执行失败则滚回
            destroy(nwe_start, new_finish); // 析构
            data_allocator::deallocate(new_start, len); // 释放申请的空间
            throw;
        }
         
        // 析构并释放原来的vector
        destroy(begin(), end());
        dealloacte(); // 成员函数  
 
        // 将迭代器指向新的vector
        start = new_start;
        finish  = new_finish;
        end_of_storage = new_start + len;
    }
}

这样就有 一些 性能消耗的 问题存在

vector 基本操作实现原理

1.pop_back

功能:剔除尾端元素 ,实现上 就是 将尾端finish往前移,将原本的尾端元素释放。

void pop_back()
{
    --finish;
    destroy(finish);
}

2.erase

功能1:清除 [first,last)的元素

实现上,就是将 [ last,finish)的元素 向前拷贝。将其从first开始放置

然后再 销毁 尾部剩余的元素,更新finish

iterator erase(iterator first, iterator last)
{
    iterator i = copy(last,finish,first);// 将[last,finish)的元素拷贝到 first为起点的内存空间,返回 拷贝最后一个元素的位置
    destroy(i, finish); // 销毁 i 到 finish 的元素
    finish = finish - (last - first);// 更新finish
    return first;
}

功能2:清除某个位置的元素

实现上,就是将该位置后面的元素全部往前移动( 和上面类似)

iterator erase(iterator position)
{
    if(positio + 1 != end())
    {
        copy(position+1,finish,position);
    }
    --finish;
    destroy(finish);
    return position;
}

可以看出 erase 清除的性能消耗 在于 copy 函数的使用,在cppreference中:https://zh.cppreference.com/w/cpp/container/vector/erase 提到了其复杂度:

3.clear

功能:清除全部

实现上就是套用 erase

void clear()
{
    erase(begin(), end());
}

4.insert

功能之一:从position开始 插入n个元素,元素初始值为 x

实现上:

1)插入的元素数量 小于等于 备用空间时

A. 插入的元素个数n < [position,finish) 的元素个数 elems_after

  • a.先将 末尾的n个现有元素往后 拷贝 即 [finish-n, finish) 拷贝到 [finish,finish+n)

  • b.更新尾部 finish = finish + n

  • c.再将剩下的 position到 刚刚finish-n 剩余的元素也往后拷贝即 [position,old_finish-n) 拷贝到 以old_finish起点的空间

  • d.将[position, position+n) 填充x值

B.插入的元素个数n >= [position,finish) 的元素个数elems_after

  • a.先在 尾部 填充 多出来的元素个数 x (多出来的元素个数指 n - elems_after)

  • b.更新尾部 finish

  • c.再将[position,old_finish) 往后拷贝,以 新的finish为起点的空间

  • d.更新尾部 finish

  • e.修改 [position,old_finish) 值为 x

2)插入的元素数量 大于 备用空间 :分成三段拼接

  • a.申请空间 len = size + n

  • b.将旧vector 的 [start,position) 拷贝到 new_start为起点的空间,得到new_finish

  • c.在 new_finish 处 填充n个x ,更新 new_finish

  • d.再将旧的vector 的 [position,finish) 拷贝 到 new_finsh为起点的空间

  • e.销毁旧vector

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
    if(n!=0)
    {
        if(size_type(end_of_storage - finish) >=n)  // 场景1)
        {
            T x_copy = x;
            const size_type elems_after = finish - position; //position后面的元素个数
            iterator old_finish = finish;
            if(elems_after > n) // 场景1) 中的 场景A.
            {
                uninitialized_copy(finish - n, finish, finish); // A.a
                finish += n;
                copy_backward(position, old_finish - n, old_finish); // A.c
                fill(position, position + n, x_copy); // A.d
            }
            else // 场景1) 中的 场景B.
            {
                uninitialized_fill_n(finish, n - elems_after, x_copy); // A.a
                finish += n - elems_after;
                uninitialized_copy(position, old_finish, finish); // A.c
                finish += elems_after;
                fill(position, old_finish, x_copy); // A.e
            }
        }
        else // 场景2)
        {
            const size_type old_size = size();
            const size_type len  = old_size + max(old_size, n); // 备用空间的翻倍
            iterator new_start = data_allocator::allocate(len); // 分配空间
            iterator new_finish = new_start;
            try // 异常情况
            {
                new_finish = uninitialized_copy(start, position, new_start); // 2).b
                new_finish = uninitialized_fill_n(new_finish, n, x); // 2).c
                new_finish = uninitialized_copy(position, finish, new_finish); // 2).d
            }
            catch(...)
            {
                destroy(new_start, new_finish);
                data_allocator::deallocate(new_start, len);
                throw;
            }
            // 2).e
            destroy(start, finish);
            deallocate();
 
            // 更新游标
            start = new_satrt;
            finish = new_finish;
            end_of_storage = new_start + len;
        }
    }
}

场景1).A 图:

场景1).B 图:

场景2) 图:

问题思考

1.insert中场景1).A 为什么需要 分成多个步骤执行?不能直接 将position之后的数据往后移动吗?

自我理解:

  • uninitialized_copy 作用是,将数据拷贝到未初始化的区域,因此 在步骤a中,只能先拷贝到 备用空间。
  • 具体内部的实现,可能 效率会比 直接将数据往后移动 高

场景1).B.a 处理上 应该也是这样的考虑。

posted @ 2021-04-13 17:53  Linese  阅读(804)  评论(0编辑  收藏  举报