- Rule7:当使用new得指针的容器时,记得销毁容器前delete那些指针
C++ 中STL的容器的析构函数不用自己调用,系统会进行析构,但是vector内元素的清空需要手动进行。
1. 非指针的数据类型,比如int,string,char,还包括自定义的数据结构、自定义的类等等,只需要手动调用vector的clear函数就行,空间的释放和系统系统就会自动进行。
2. 指针类型的数据,这种情况需要手动进行释放,也就说new产生的内存需要手动使用delete进行释放。
这一部分需要详细进行讲解:
首先将 在VS开发中检测内存泄露的函数,
_CrtDumpMemoryLeaks();
这个函数放在你需要检测内存段的后面,也就说他可以自动检测在它代码前的所有程序内存泄露情况。
举一个例子:
void test()
{
vector<Widget*> vpw;
for (int i=0;i<1000;i++)
{
vpw.push_back(new Widget);
}
vpw.clear();
}
int _tmain(int argc, _TCHAR* argv[])
{
test();
_CrtDumpMemoryLeaks();
return 0;
}
在vs中程序能够运行,没有报错,但是可以很好看出,vector里面数据是指针类型,需要手动释放指针所指向的数据。否则里面的数据就会变成野数据,没有释放内存资源。
如果不通过_CrtDumpMemoryLeaks()可能你得不到任何提示。
程序运行效果如下:
在“输出”选项卡中我们截取了一部分内存泄露的输出显示。
我们说过vector本身的析构函数是在作用域结束后自动调用的,我们看在如下的代码:
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> vpw;
for (int i=0;i<1000;i++)
{
vpw.push_back(i);
}
vpw.clear();
_CrtDumpMemoryLeaks();
return 0;
}
vector里面的元素是基本元素类型,理应与内存泄露没有任何联系,我们不用去理会。
但是:这是程序的output“输出”选项卡的内容:
这里面出现了两次内存泄露,可以直接分析出 这包括了所有int元素,及vector变量本身。
??为什么呢?
因为 vector释放内存的时间是失去作用域后,但是如上时刻调用_CrtDumpMemoryLeaks()没有失去作用域呀!!!。为了测试:
可以把上述代码封装到一个测试函数中或者加上{}作为一个内部作用域。
我们再回到带有指针的vector内存释放的问题上来,当我们调用vector.clear()时,内部的指针数据的确是自动释放了,但是,指针所指向的数据却没有被释放掉,这一部分需要手动进行释放,毕竟他不能自己自动调用delete方法。
修正后的操作如下:
void test()
{
vector<Widget*> vpw;
for (int i=0;i<1000;i++)
{
vpw.push_back(new Widget);
}
for(vector<Widget*>::iterator it = vpm.begin();it!=vpm.end();it++)
{
delete *it;
*it = NULL;
}
vpw.clear();
}
在看如下的代码:
通过调用clear()函数,vector的存储量没有发生改变。
swap()是交换函数,使vector离开其自身的作用域,从而强制释放vector所占的内存空间,总而言之,释放vector内存最简单的方法是vector< int >.swap(nums)。
容器中的成员函数swap
在容器vector中,其内存占用的空间是只增不减的,比如说首先分配了10,000个字节,然后erase掉后面9,999个,则虽然有效元素只有一个,但是内存占用仍为10,000个。所有内存空间在vector析构时回收。
一般,我们都会通过vector中成员函数clear进行一些清除操作,但它清除的是所有的元素,使vector的大小减少至0,却不能减小vector占用的内存。要避免vector持有它不再需要的内存,这就需要一种方法来使得它从曾经的容量减少至它现在需要的容量,这样减少容量的方法被称为“收缩到合适(shrink to fit)”。(节选自《Effective STL》)如果做到“收缩到合适”呢,此时就需要使用swap了。
STL本身没有包含引用计数指针,智能指针出错的不同方式有很多,一个经过检验后的智能指针是Boost库中的shared_ptr,引用Boost中的shared_ptr,可以这样使用:
typedef boost::shared_ptr<Widget> SPW;
vector<SPW> vwp;
for(int i=0;i<SIZE;i++)
vwp.push_back(SPW(new Widget));
如果需要使用引用指针,你是不能够使用auto_ptr这个智能指针的。如果不需要引用指针,我们可以使用auto_ptr,比如如下代码也不会发生内存泄露的问题。
typedef auto_ptr<Widget> WAP;
vector<WAP> vpw;
for (int i=0;i<1000;i++)
{
vpw.push_back(WAP(new Widget));
}
我们需要知道的是所STL容器很智能,但没有智能到知道时候应该删除它们所包含的指针,当你要删除指针的容器时要避免资源泄漏,你必须用智能引用计数指针对象,比如Boost的shared_ptr来替代指针,或者你必须在容器销毁前手动删除容器中的每个指针。
- Rule8:永不建立auto_ptr的容器
前面我们都提到过auto_ptr,但是我们看在Effective STL中作者对于auto_ptr的看法。
auto_ptr的容器(COAPs)是禁止的,试图使用它们的代码都不能编译,C++ 标准委员会花费了无数努力来安排这种情况。
COAPs不可移植。
C++标准禁止他们,比较好的STL平台已经实现了。可以有足够理由推断随着时间的推移,目前不能实现标准的这个方面的STL平台将变得更适应,并且当那发生时,使用COAPs的代码将更比现在更不可移植。
当然如果你不用考虑移植性的话,auto_ptr所指向对象的所有权被转移到拷贝的auto_ptr上,而被拷贝的auto_ptr被设为NULL,拷贝一个auto_ptr将改变他的值。
这非常不寻常,也许它很有趣,但你(作为STL的用户)关心的原因是它导致一些非常令人惊讶的行为。例如,考虑这段看起来很正确的代码,它建立一个auto_ptr< Widget >的vector,然后使用一个比较指向的Widget的值的函数对它进行排序。
bool widgetAPCompare(const auto_ptr<Widget>& lhs,
const auto_ptr<Widget>& rhs) {
return *lhs < *rhs; // 对于这个例子,假设Widget
} // 存在operator<
vector<auto_ptr<Widget> > widgets; // 建立一个vector,然后用Widget的auto_ptr填充它;记住这将不能编译!
sort(widgets.begin(), widgets.end(),// 排序这个vector
widgetAPCompare);
如果在代码中需要把一个元素从保存的区间拷贝到局部临时对象中,就会发生不可预知的问题,因为一旦发生拷贝,原始的auto
_ptr就会变成NULL,再次使用就会出现问题。
这就是为什么标准委员会会建议永不建立auto_ptr的内容,即使你的STL平台允许你那么做。比如VS中支持auto_ptr的使用。