vector的内存释放

1.vector内存分配机制

C++中vector的一个特点是: 内存空间只会增长,不会减小。即为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都挨着前一个元素存储。设想,如果每次vector添加一个新元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间,这样性能就会非常慢。
  所以,实际上分配时其容量要比当前所需容量更多,即vector预留了一些额外的存储区,这样就不必单独为每个新元素重新分配内存空间,减少开销。 另外,在vector中内存只增不减体现在 - 比如首先分配了10000个字节,然后erase掉后面9999个,留下一个有效元素,但是实际上内存占用仍然为10000个,所有内存空间是在vector析构的时候才能被系统回收。所以,即使使用clear,vector所占用的内存空间依然如故,无法保证内存的回收。
  当然,对于数据量很小的vector,完全没有必要进行主动释放,就比如200 * 200的网格计算,就是没有必要的,但是如果是1000 * 1000,那么就是前者的25倍了,这时就需要进行主动内存释放了。
 另外,既然要进行内存释放,我们不得不掌握size()和capacity()方法的区别,前者是实际的vector元素个数,后者是实际占用内存的个数,一般来说,capacity()是大于或等于size()的。
 通过分析 vector 容器的源代码不难发现,它就是使用 3 个迭代器(可以理解成指针)来表示的:

//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
    ...
protected:
    pointer _Myfirst;
    pointer _Mylast;
    pointer _Myend;
};

在这里插入图片描述

如图 1 所示,通过这 3 个迭代器,就可以表示出一个已容纳 2 个元素,容量为 5 的 vector 容器。

在此基础上,将 3 个迭代器两两结合,还可以表达不同的含义,例如:
_Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
_Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
_Myfirst 和 _Myend 可以用表示 vector 容器的容量。
对于空的 vector 容器,由于没有任何元素的空间分配,因此 _Myfirst、_Mylast 和 _Myend 均为 null。

vector<int> nums;
nums.resize(10);
nums.reserve(20);

vector扩容机制三步走另外需要指明的是,当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:

  1. 完全弃用现有的内存空间,重新申请更大的内存空间
  2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中
  3. 最后将旧的内存空间释放
    这也就解释了,为什么 vector 容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效的原因。
    由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。

vector 容器扩容时,不同的编译器申请更多内存空间的量是不同的。以 VS 为例,它会扩容现有容器容量的 50%,根据等比数列性质可知,这样做可以复用前面的内存空间。
即1+2+4 < 2 * 4=8;而1+1.5+2.25=4.75>2.25 * 1.5=3.375。

释放方法

vector<int> nums; 
nums.push_back(1);
nums.push_back(1);
nums.push_back(2);
nums.push_back(2); 
vector<int>().swap(nums); 
或者nums.swap(vector<int> ())

或者是加括号,人为得为其限定作用域,在离开作用域后自动析构

//加一对大括号是可以让tmp退出{}的时候自动析构
{ 
    std::vector<int> tmp =   nums;  
    nums.swap(tmp); 
}

综合应用:

int main(){

	vector<int> nums;
	cout << nums.size() << "    " << nums.capacity() << endl;
	for (int i = 0; i < 10; i++) {
		nums.push_back(i);
		cout << nums.size() << "    " << nums.capacity() << endl;
	}
	cout << nums.size() << "    "<< nums.capacity() << endl;
	nums.clear();
	cout << nums.size() << "    " << nums.capacity() << endl;
	nums.swap(vector<int>());
	cout << nums.size() << "    " << nums.capacity() << endl;
	nums.reserve(15);
	nums.resize(2);
	cout << nums.size() << "    " << nums.capacity() << endl;


	return 0;
}

输出结果
在这里插入图片描述

2.vector中不是基础数据成员

当时如果nums是一个的成员,不能把vector.swap(nums)写进类的析构函数中,否则会导致double free or corruption (fasttop)的错误,原因可能是重复释放内存。标准解决方法如下:

template < class T >
void ClearVector( vector< T >& vt ) 
{
    vector< T > vtTemp; 
    veTemp.swap( vt );
}

如果vector中存放的是指针,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:

#include <vector> 
using namespace std; 

vector<void *> v;

每次new之后调用v.push_back()该指针,在程序退出或者根据需要,用以下代码进行内存的释放:

for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++) 
    if (NULL != *it) 
    {
        delete *it; 
        *it = NULL;
    }
v.clear();

那么问题来了,假如在vector中存放含有指针的结构体呢
这是一个没有析构函数的版本

struct havep {
	int a;
	int *p;
	havep() {
		p = new int(777);
	}
};

int main(){
	vector<havep> test;
	havep a1;
	havep a2;
	test.push_back(a1);
	test.push_back(a2);
	test.clear();

	system("pause");
}

在这里插入图片描述
以刚才的形式向容器中直接放结构体,发生了某种意义上的浅拷贝。
继续运行,clear后,a1,a2中的p指向内容不被改变。
在这里插入图片描述

添加析构函数:

~havep() {
	delete p;
	p = nullptr;
}

再次运行,clear后报错。所以这种直接放结构体的形式比较危险,浅拷贝时往往会发生未知错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值