对于数组,如果我们去实现按位置 position 插入 n 个值为 x 的元素,怎么做?假设空间充足,一般的做法是:[position,last)所有元素顺次后移 n 个位置,然后 [position, position+n) 填充成 x 。常规思维也能达到目的,但是大师到底是大师,他们重重考虑了效率,面对最适合的情形总是选择最优的解决办法。稍后我们就可以看到 Alex Stepanov 以及 Matt Austern,David Musser 等巨匠他们是如何实现的。
如果对于这一块知识有任何意见或建议的话,欢迎在博客下方留言或者直接致电 [email protected]。源码剖析的道路崎岖难行,但是欲带皇冠必承其重,这是我们应该付出的而且值得的代价。
在 SGI STL中,Alex Stepanov 以及 Matt Austern,David Musser 等巨匠严格将空间分配和回收、对象的构造和析构区分开来。当我们去new(new operator)一个对象的时候,步骤是这样的:
我们也很简单就将 new operator 和 operator new 区分开了啊。new operator 负责内存申请和对象构造,operator new 只负责内存申请类似于 malloc 。配置内存交付给空间配置器 alloc 来完成,在本篇博客主要讨论的是面对已经拿到的内存,如何进行对象的构造,也即完成 placement new 的功能。空间配置器 alloc 是一个很庞大的内容,本博客不予展开。
现在我们已经拿到申请到手的堆内存了,如何使用它?也就是说,我跳过空间配置器没有进行谈论。
迭代器一共有5种(InputIterator / OutputIterator / ForwardIterator / BidirectionalIterator / RandomIterator)。迭代器是一种模仿部分或全部 raw pointer 行为的对象。(行为有p-> *p ++或-- p[n] p+n或p-n p1-p2 p1 任何一个迭代器,其类型应该永远落在“该迭代器所隶属之各种类型中最强化的那个”。意思就是说呢,如果一个迭代器类型是RandomIterator,那么不要把它称作 InputIterator(虽然也没错)。不可以将InputIterator赋值给RandomIterator。这里需要指明另一个重要的概念,向上转换和向下转换的知识点。 向上转换:派生类--->基类;向下转换(不建议): 基类--->派生类。向上转换一定是安全的,无非就是派生类中的非基类部分丢失了而已,向下转换不一定是安全的,因为派生类如果有所扩展,会导致派生类的非基类部分残缺。 注意揣摩源码参数列表的迭代器类型,不是随便给定的,必须达到最低要求。 低层次函数面对的是未初始化的内存,操作未初始化内存和已初始化内存是否有区别视情况而定。 C++中认为一切变量都是对象,int型变量也是对象。作为对象,就自然而然有4个特殊的成员函数,default ctor、copy ctor、operator assignment、dtor,Alex Stepanov 以及 Matt Austern,David Musser 等巨匠通过类型萃取,能够判断一个对象的4个成员函数是否是 trivial 。 为什么要分的这么细呢?如果一个对象的构造函数没有什么实际意义,那么就不要调用;直接调用memmove(内存直接复制行为)乃是最佳选择。怎么理解呢?就拿内置类型 int 来说吧,进行内存复制和调用构造没区别啊,所以选择内存复制因为它快。不得不感叹 Alex Stepanov 以及 Matt Austern,David Musser 等巨匠的思维是如此的缜密。 高层次函数享受低层次函数提供的服务,也就是说低层次函数有很多运行流程,流程之一就是调用上层函数。 伤其十指不如断其一指,我将源码剖析的范围局限在 vector 剩余空间充足的情况,剩余空间不充足就需要进行扩容(日后再说)。 代码片段告诉我们,一共分三步: 第一步:在源区间末尾切出一块同待插入区间等长的部分,整体后移;因为源区间末尾之后面对的是未初始化的内存,所以调用 uninitialized_copy 。 第二步:第一步的剩余部分逆向拷贝的方式,整体右移;因为是对已初始化空间的操作而且是逆向拷贝,所以调用copy_backward 。 第三部分:第二部分的空洞部分由待插入序列进行填充。因为是对已初始化空间的操作,所以调用 fill 。 第一步:整体后移;因为面对的是未初始化的内存,所以调用 uninitialized_fill_n 。 第二步: 将待插入区间的相应部分进行填充,因为面对的是未初始化的内存,所以调用 uninitialized_copy 。 第三步:将待插入区间的剩余部分进行填充,因为面对的是已初始化的内存,所以调用 fill 。 【1】STL模板库中vector的用法 【2】详解vector内部机制
1.2 低层次函数
//拷贝函数:完成源区间[first, last)的内容向目标区间[result, result + (last-first) )的拷贝。为copy函数提供服务。
template
1.3 高层次函数
//重载或多载函数有特化版本可以被调用,是不会优先调用模板版本的。
//将源区间[first, last)的内容向目标区间[result, result+(last-first))进行复制。
template
二、vector::insert()源码剖析
//将n个元素x插到position位置之前。//STL规定,区间描述时一律左闭右开(半开半闭),[start, finish)
template
2.1 插入点之后元素个数大于新增元素个数
if( elems_after > n) //插入点之后现有元素个数 大于新增元素个数(n)。
{
//将源区间[finish-n, finish)填充到目标区间[finish, finish+n)。
uninitialized_copy(finish-n, finish, finish);
finish += n; //尾端标记后移。
//将源区间[position,old_finish-n)从逆向填充到目标区间[old_finish-n, old_finish)。
copy_backward(position, old_finish-n, old_finish);
//将目标区间[position, position+n)填充成x_copy。
fill(position, position+n, x_copy);
}
2.2 插入点之后的元素个数小于等于新增元素个数
else //插入点之后现有元素个数 小于等于 新增元素个数(n)。
{
//将[finish, finish+(n-elems_after))全部填充成x_copy。
uninitialized_fill_n(finish, n-elems_after, x_copy);
finish += n- elems_after; //尾端标记后移。
//将源区间[position, old_finish)填到目标区间[finish, finish+(old_finish-position))
uninitialized_copy(position, old_finish, finish);
finish += elems_after; //尾端标记后移。
//将目标区间[position, old_finish)填充成x_copy。
fill(position, old_finish, x_copy);
}
三、参考资料