问题实例
</br>
假设我们有一个关于投资的继承体系,其基类为1
class Investment {...};
我们惯用factory function来产生特定的对象,并且有责任释放该对象,现有某个f函数执行了该操作:1
2
3
4
5void f(){
Inverstment* pInv = createInvestment();//调用factory function
...
delete pInv;
}
上述程序看起来没毛病,但是实际使用中可能会发生内存泄漏。原因可能但不仅仅如下:
- …处有了一个过早的return
- delete位于某个循环或者判断内并未能执行到
- …处抛出了一个异常
我们泄漏的不仅仅只是内含投资对象的那一块内存,还包括任何它所保存的资源。
当然,完美的逻辑可以避免上述问题,但关键在于代码总是处于不断修改和升级的过程中,单纯依赖delete语句总被执行是不可能的。
解决方案
</br>
为了确保资源总是被释放,我们需要将资源放进对象内。当控制流离开程序块时,该对象的析构函数则会自动释放那些资源。
auto_ptr
许多资源被动态地分配于heap内,然后被用于单一区块或函数内。这些资源应该在控制流离开那个区块或者函数时被释放。标准库提供的auto_ptr正是针对这种形式而设计的。
auto_ptr是一个智能指针,其析构函数自动对其所指对象调用delete。(auto_ptr的具体描述及特性,详见C++ Primer)
其具体使用大致如下:1
2
3
4void f(){
auto_ptr<Investment> pInv(createInvestment());
...
}
以对象管理资源的关键思路在于:
- 获得资源后立刻放入管理对象。
这也被称为“资源获取之日即是初始化之时”(resource acquisition initialization;RAII)
有时候我们也把拿到的资源用来赋值而不是初始化。但不管怎么说,每一笔资源总在获得之时就被立刻放入管理对象中。 - 管理对象(managing object)运用析构函数确保资源释放。
除非管理对象析构函数里会抛出异常,否则资源总能被正确释放。针对唯一的这一种例外,Effective C++ 8 对此有详细的描述
auto_ptr的缺陷
如果通过copy构造函数或者copy assignment操作符来复制auto_ptr,那它们会变成null,新复制的智能指针将获得资源的唯一使用权。
这种诡异的复制行为直接导致STL容器不能使用auto_ptr(具体论述见Effective STL 8)
shared_ptr
auto_ptr的替代方案是用引用计数型智能指针。也就是reference-counting smart pointer(RCSP)。它持续地追踪有多少对象指向某个资源,没人指向就释放资源。
shared_ptr的缺陷
无法打破环状引用(例如两个没人用的对象彼此互指,编译器判断对象仍然处于使用状态)。
智能指针的缺陷
智能指针的析构函数中做的是delete而非delete[],这意味着在动态分配而得到的array身上用它们效果感人。1
shared_ptr<int> spi(new int[1024]);//析构时只释放了一个
但这个问题不大,Effective STL 13提及,应当尽可能使用vector与string替换动态数组。
总结
</br>
本节虽然大量提及了智能指针,但实际上RAII和智能指针并无关联,考虑Effective STL 12中提到的多线程实例,其中Lock对象也负责了资源的管理。