zoukankan      html  css  js  c++  java
  • 《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 处理上 应该也是这样的考虑。

  • 相关阅读:
    统计单词Java
    信息反馈—冲刺08
    信息反馈—冲刺07
    PHP连接MySQL创建表
    phpstrom php出现404
    第十周总结
    信息反馈—冲刺06
    信息反馈—冲刺05
    无所心态,没环境下的自学
    centos6.5 nginx安装pcre错误
  • 原文地址:https://www.cnblogs.com/q1076452761/p/14654494.html
Copyright © 2011-2022 走看看