SGISTL源码阅读九 Vector容器中

SGISTL源码阅读九 Vector容器中

前言

在上一篇文章中我们了解了Vector的基本结构、构造以及内存分配,但是对于Vector的动态性并未涉及。
接下来我们继续学习vector的一些相关操作,从中我们可以看到vector是如何进行内存控制的


深入源码

push_back
void push_back(const T& x) {  
	//判断vector的使用量是否达到最大值
    //如果还有多余的空间,则直接调用construct构造,并且维护vector的迭代器
    if (finish != end_of_storage) {
        construct(finish, x);
        ++finish;
    }
    //否则调用insert_aux
    else
        insert_aux(end(), x);
  }
insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
	//判断vector的使用量是否达到最大值
    //这段代码的作用是将传入的值x放到指定位置(position上去)
    //你可能会疑惑这个if我们在push_back中不是已经判断了吗为啥还要判断一次
    //因为insert_aux不仅仅只会被push_back调用
    if (finish != end_of_storage) {
    	//finish指向的是未使用空间的头, 所以将finish上的值初始化为finish-1上的值,并讲finish++
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        //将vector上的数依次往后挪,留出position位置
        copy_backward(position, finish - 2, finish - 1);
        //给position位置赋值
        *position = x_copy;
    }
    //处理容量不够的情况
    else {
        const size_type old_size = size();
        //这里处理了旧容量为0的情况
        const size_type len = old_size != 0 ? 2 * old_size : 1;
        //使用空间配置器重新申请一个两倍大小的空间(或者是1个)
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        __STL_TRY {
        	//初始化操作,将原来的vector拷贝一份到新的空间上
            new_finish = uninitialized_copy(start, position, new_start);
            construct(new_finish, x);
            ++new_finish;
            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 */
		//销毁元素,释放原来的空间并且更新vector的迭代器,扩容完成
        destroy(begin(), end());
        deallocate();
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
  }
}

insert_aux中我们清楚地看到了vector对容量的管理,以及那三个迭代器的作用。
只要容量不够用了,我们就申请一个原vector2倍大小的空间,将原vector的数据拷贝到新的空间后释放,最后维护vector的三个迭代器。
但是这也有可能会造成迭代器失效的问题,比如说我们定义了一个迭代器指向vector中的元素,vector在我们不知晓的情况下进行了扩容,原来的空间被释放了,迭代器就指向了一个非法地址。

insert

insert有以下几个版本

  //指定位置插入一个值为x的元素
  iterator insert(iterator position, const T& x) {
    size_type n = position - begin();
    //如果容量够用且所插入的位置是最后一个
    if (finish != end_of_storage && position == end()) {
      construct(finish, x);
      ++finish;
    }
    //如果容量不够用或者插入的位置不是最后一个(insert_aux中处理了这种情况)
    else
      insert_aux(position, x);
    return begin() + n;
  }
  //仅仅指定了位置,则插入一个指定类型的默认构造对象
  iterator insert(iterator position) { return insert(position, T()); }
#ifdef __STL_MEMBER_TEMPLATES
  //范围插入在指定位置插入一段由first和last迭代器指定范围的数据
  template <class InputIterator>
  void insert(iterator position, InputIterator first, InputIterator last) {
    //调用range_insert(之后也会讲到)
    range_insert(position, first, last, iterator_category(first));
  }
#else /* __STL_MEMBER_TEMPLATES */
  //同为范围插入
  void insert(iterator position,
              const_iterator first, const_iterator last);
#endif /* __STL_MEMBER_TEMPLATES */
  //指定位置插入n个值为x的元素
  void insert (iterator pos, size_type n, const T& x);
  //重载版本
  void insert (iterator pos, int n, const T& x) {
    insert(pos, (size_type) n, x);
  }
  //重载版本
  void insert (iterator pos, long n, const T& x) {
    insert(pos, (size_type) n, x);
  }

总的来说insert操作分为三种

  • 指定位置插入一个元素(在上面的源码中我们介绍过了)
  • 指定位置插入n个元素
  • 以迭代器指定范围插入

指定位置插入n个元素
代码和迭代器之情范围插入极其类似,我们只分析其中之一。
以迭代器指定范围插入
根据条件编译分不同的函数,但是实现都是一样的,range_insert和下面insert的代码一致

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position,
                              const_iterator first,
                              const_iterator last) {
  if (first != last) {
    size_type n = 0;
    //调用distance求出迭代器指定范围元素个数
    distance(first, last, n);
    //如果剩余容量足够,则将元素插入并维护vector的迭代器
    if (size_type(end_of_storage - finish) >= n) {
      const size_type elems_after = finish - position;
      iterator old_finish = finish;
      if (elems_after > n) {
      	//初始化未初始化空间
        uninitialized_copy(finish - n, finish, finish);
        finish += n;
        //将原vector相应位置向后移动,留出n个位置供插入
        copy_backward(position, old_finish - n, old_finish);
        //插入元素
        copy(first, last, position);
      }
      //如果position的位置刚好在原vector的末尾,则直接插入
      else {
        uninitialized_copy(first + elems_after, last, finish);
        finish += n - elems_after;
        uninitialized_copy(position, old_finish, finish);
        finish += elems_after;
        copy(first, first + elems_after, position);
      }
    }
    //如果剩余容量不足的情况
    else {
      const size_type old_size = size();
      //申请至少原vector容量的两倍空间(如果n的值大于old_size则会超过两倍)
      const size_type len = old_size + max(old_size, n);
      iterator new_start = data_allocator::allocate(len);
      iterator new_finish = new_start;
      __STL_TRY {
        //将原vector头至插入点position前的元素复制到新空间
        new_finish = uninitialized_copy(start, position, new_start);
        //将需要插入的元素复制到新空间
        new_finish = uninitialized_copy(first, last, new_finish);
        //将原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 */
	  //将原vector的元素销毁并释放空间,并且维护vector的三个迭代器,完成扩容
      destroy(start, finish);
      deallocate();
      start = new_start;
      finish = new_finish;
      end_of_storage = new_start + len;
      }
  }
}

总结

通过以上的源码学习,我们了解到了vector的内存控制机制,并且讲到了vector的插入操作。
可见vector的动态性其实也只是一个假象,需要将原来的空间释放并且申请一块更大的空间。
之后我们将继续学习vector的相关操作。

猜你喜欢

转载自blog.csdn.net/lyn_00/article/details/83998791