STL之vector的push_back过程详解
最近,被⾯试官的⼀道题问倒,很失落,明明看过《STL源码分析》,为啥这种问题还没答好,只能说⾃⼰看的时候没有仔细去思考。这道题就是标题的问题,⾯试完我重新看了⼀遍《STL源码分析》中关于这块的内容,这⾥记录下⾃⼰看完的⼀点理解。
在STL中,⼀般对容器的内存分配和构造是分开的2个过程,STL有专门的空间配置器负责分配内存,⽽构造则是通过placement new在已申请的内存上进⾏的,vector也不除外,下⾯是vector的push_back函数源码:
template <class T, class Alloc = alloc>
在职教育硕士分数线void vector::push_back(const T& x)
英文名字男{
if (finish != end_of_storage)ogallala
{
construct(finish, x);
学雅思哪个机构好
++finish;金山快译在线翻译
}
el
{
inrt_aux(finish, x);walk away
}
}
其中,construct是STL的全局函数,是所有容器共⽤的,它的具体实现如下:
template<class T1, class T2>
inline void construct(T1* p, const T2& value)
{
new(p) T1(value); //placement new, 调⽤T1::T1(value);
}
⽽inrt_aux是vector⾃⼰的成员函数,具体实现如下:
template <class T, class Alloc = alloc>
void vector<T, Alloc>::inrt_aux(iterator position, const T& x)
{
//还有备⽤空间
if (finish != end_of_storage)
{
//在备⽤空间起始处构造⼀个元素,并以vector最后⼀个值为其初值
construct(finish, *(finish - 1));
大连日本留学中介/
/调整⽔位
++finish;
//拷贝⼀个元素
T copy_x = x;
//把vector插⼊位置position之后的元素往后移⼀位
copy_backward(position, finish - 2, finish - 1);
//给position指向的地⽅赋值,赋值内容为前⾯拷贝的元素
*position = copy_x;
}
//已⽆备⽤空间
el
{
const size_type old_size = size();
//如果原来的vector为空,则申请⼀个元素的空间,否则申请可以容纳原来2倍元素的空间 const size_type len = old_size == 0 ? 1 : 2 * old_size;
//全局函数,申请空间
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
try
{
//将原来vector的position之前的内容拷贝到新的vector前⾯
new_finish = uninitialized_copy(start, position, new_start);
//调⽤构造函数为新插⼊的元素赋值
construct(new_finish, x);
//调整⽔位
++new_finish;
//将原来vector的postion之后的内容拷贝到新的vector后⾯
new_finish = uninitialized_copy(position, finish, new_finish);
}
catch (...)
{
//析构
destroy(new_start, new_finish);
//释放刚刚申请的内存
shalala
data_allocator::deallocate(new_start, len);
throw;
}
//析构原vecotr
destroy(begin(), end());
//释放原vector的空间
deallocate();
//调整迭代器指向新的vector
yahostart = new_start;
finish = new_finish;
end_of_storage = new_start +len;
}
}
上⾯空间配置函数allocate的最底层实现⽐较复杂,但是只要记住2点即可,(1)如果申请的空间⼤于128字节,就直接调⽤malloc申请,(2)如果申请的空间⼩于等于128字节,就从STL维护的16条free list⾥⾯寻找合适的⼀块内存使⽤(这16条free list各⾃管理⼤⼩分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节的⼩额内存块,之所谓维护这些list既是为了防⽌频繁调⽤malloc,也是为了避免太多⼩额区块造成内存的碎⽚)。
介绍了这么多,回到最初的问题,如果我们⼀开始定义了⼀个空的vector,然后现在要往⾥⾯push_back⼀个个对象,这个过程具体是怎样的,请看下⾯这段代码的注释,这些都是我重新看了⼀遍书的理解:
//定义⼀个类A
class A{
int x;
double y;
};
//定义⼀个空的vector,vector中可以存放的是类A的对象
vector<A> vec;
//定义类A的对象a,对象b,对象c和对象d
A a;
A b;
A c;
A d;
/*
注意:根据上⾯对push_back的源码分析可知,
因为⼀开始vec是空的,所以会⾛inrt_aux函数的⽆可⽤空间的分⽀,
调⽤allocate申请⼀块能够容纳《⼀》个A类对象的内存,
并调⽤拷贝构造函数把a赋值给vec的finish迭代器指向的内存,
说⽩了就是vec中存放的a和上⾯定义的a对象已经不是⼀个东西了。
*/
vec.push_back(a);
/*
注意:根据上⾯对push_back的源码分析可知,
现在vec也没有多余的可⽤空间,所以会再次⾛inrt_aux函数的⽆可⽤空间的分⽀,
调⽤allocate申请⼀块能够容纳《两》个A类对象的内存,
把原来的vec的唯⼀⼀个元素a移动到新的vec上去,
并调⽤拷贝构造函数把b赋值给新的vec的finish迭代器指向的内存,这时a和b存放在相邻内存中。
*/
vec.push_back(b);
/*
注意:根据上⾯对push_back的源码分析可知,
现在vec也没有多余的可⽤空间,所以会再次⾛inrt_aux函数的⽆可⽤空间的分⽀,
调⽤allocate申请⼀块能够容纳《四》个A类对象的内存,
把原来的vec的两个元素a和b移动到新的vec上去,
并调⽤拷贝构造函数把c赋值给新的vec的finish迭代器指向的内存,
这时a和b和c存放在相邻内存中,⽽且这块内存还能再容纳⼀个类A对象。
*/
vec.push_back(c);
/
*注意:根据上⾯对push_back的源码分析可知,
现在vec还有⼀个可⽤空间,所以这次会⾛construct函数的分⽀,
通过placement new在已有的内存上调⽤拷贝构造函数把d赋值给finish迭代器指向的内存,
这时a和b和c和d存放在相邻内存中,但这块内存⼜再次没有剩余空间了。
*/
vec.push_back(d);
itouch是什么意思就写到这⾥吧,继续⾯壁思过了。