C++vector插入时的内存分配
今天在看侯建的《STL源码剖析》时看到插入操作时发现多了一个拷贝构造的时候,有点疑惑,于是自己做了一个实验,对vector的push_back()操作时的内存分配进行了仔细的了解。
vector的push_back()操作原理
简单来说,push_back()就是往vector之后插入元素,这也是vector跟array最大的区别,array只能是固定大小,而有了vector之后,就可以动态扩大其容量了。
这里解释一下vector插入时的一些基本原理。
vector中元素与待插入元素绝对不是同一个
对象
因为vector使用的alloc(用于分配内存)一直是从堆或者内存池(内存池应该是也属于堆)中获取内存,所以vector中所有的元素肯定不是位于栈上,就是我有一个int a=3
,那么a肯定是位于栈上的,但是vector<int>b;b.push_back(a);
之后,b[0]虽然值等于a,但是和a已经不是一个对象了,vector会在堆上给其重新分配内存。
vector的capacity属性
vector的capacity属性可由vector<int>b;...;b.capacity();
获得,代表了当前vector分配的大小;也就是当前vector中所能容纳的元素的个数。vector在插入时,如果:插入数量+已有数量<=vector.capacity()
就直接插入,而插入数量+已有数量>vector.capacity()
时就需要申请更多的内存了。那么究竟是如何申请内存呢?
实验
我们会依次尝试向vector中插入元素。查看vector究竟是如何分配内存的。
测试代码块
#include<iostream>
#include<vector>
using namespace std;
class A{
private:
static int a; //标识A的数量
public:
A(){
a++;
cout<<a<<" "<<this<<endl;//显示新构造的A的对象的地址
}
A(const A &b){
a++;
cout<<a<<" "<<this<<endl;//显示由拷贝构造函数构造的A的对象的地址
}
~A(){
cout<<this<<" has been destoried!\n";//显示被析构掉的对象的地址
}
};
int A::a=0;
int main(){
vector<A>re;
A s1;
re.push_back(s1);
cout<<"A capacity is :"<<re.capacity()<<" "<<&re[0]<<endl;//输出re分配的大小和首元素的地址
cout<<endl;
A s2;
re.push_back(s2);
cout<<"A capacity is :"<<re.capacity()<<" "<<&re[0]<<endl;
cout<<endl;
A s3;
re.push_back(s3);
cout<<"A capacity is :"<<re.capacity()<<" "<<&re[0]<<endl;
cout<<endl;
A s4;
re.push_back(s4);
cout<<"A capacity is :"<<re.capacity()<<" "<<&re[0]<<endl;
cout<<endl;
A s5;
re.push_back(s5);
cout<<"A capacity is :"<<re.capacity()<<" "<<&re[0]<<endl;
cout<<endl;
return 0;
}
运行结果
实验分析
构造S1调用了普通构造函数,但是把他放入vector是调用的却是拷贝构造函数,而且拷贝构造函数构造出来的对象就是vector[0];
构造S2是也是调用了普通构造函数,但却调用两次拷贝构造函数,和依次析构函数,显然可以看到析构掉的是之前的vector[0],而两个拷贝构造其中一个就是vector[0],那么另一个显然是vector1,就是新插入的元素。可以看出,当插入数量+已有数量>vector.capacity()
时,系统会现将原有的vector中所有元素拷贝一份到新的地址中,然后将之前的vector中的元素全部析构掉。其实从《STL源码分析》中可以看到,此时vector中的start,finish和end_of_storage三个指针也同步更新了。
以此类推,我在最后依次插入S5的时候,因为5>re.capacity()=4
,所以程序调用拷贝构造函数重新构造了之前的四个对象,当然刚开始还是构造了要push_back()进去的S5。最后再将之前的vector中的元素4个元素全部析构掉。
结论
vector在插入元素的时候,首先调用拷贝构造函数在堆中申请内存构造对象,如果插入对象数量没有超过vector的capacity,就直接插入;若超过了就在调用拷贝构造函数将之前vector中所有的元素都重新构造一边放入新的内存中,然后将之前vector中所有的元素全部析构掉,并释放内存。