第7条 如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉。
如果一个容器包含指针,则析构函数不对指针做任何事情,不会对指针所指的对象做delete操作,所以会引起内存泄露
对于解决方法,文章采用一步一步引进的方式,由一个方法引出另一个方法,
如用代码写出:
class Widget
{
public:
private:
};
void doSomething()
{
vector<Widget *> vwp;
for (int i = 0; i < size; ++i)
vwp.push_back(new Widget);
}//如果直接退出会引起资源泄露
//解决方法1,函数中添加循环。
//缺点:这段代码不是异常安全的,如果在向vwp中填充指针和从中删除指针
//的过程中有异常抛出的话,同样有资源泄露
void doSomething()
{
vector<Widget *> vwp;
for (int i = 0; i < size; ++i)
vwp.push_back(new Widget);
for (vector<Widget*>::iterator i = vwp.begin(); i != vwp.end(); ++i)
delete *i;
}
//解决方法2:将循环变成for_each,将delete变成一个函数对象。
//缺点:需要指定要删除的类型,本例中为Widget
template<typename T>
struct DeleteObject: public unary_function<const T*, void>
void operator()(const T* ptr) const
{
delete ptr;
};
void doSomething()
{
vector<Widget *> vwp;
for (int i = 0; i < size; ++i)
vwp.push_back(new Widget);
for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>());
}
//这种方法存在的隐患是,如果里面写的是一个基类的对象,则会出现使用基类的指针删除派生类的对象
//当基类没有虚析构函数,则出现不确定行为,如下
class SpecialString : public string{...};
//同标准的STL容器一样,string没有虚析构函数,而从没有虚析构函数的类进行公有继承是C++的一项重要禁忌
void doSomthing()
{
deque<SpecialString*> desp;
...
//不确定的行为!通过基类的指针删除派生类对象,而基类又没有虚构函数,因为两个类很像,所以在使用过程中可能出错。
for_each(dssp.begin(), dssp.end(), DeleteObject<string>());
}
//对于这种情况的解决方法是:
//解决方法3:通过让编译器推断出传给DeleteObject::opertator()的指针类型,需要做的是将模板化从DeleteObject移到它的operator()中
struct DeleteObject
{
template<typename T>
void operator()(const T* ptr) const
{
delete ptr;
}
};
//这种方法舍弃了可配接的能力 ,调用方法是
void doSomthing()
{
deque<SpecialString*> desp;
...
for_each(dssp.begin(), dssp.end(), DeleteObject()); //编译器知道传给operator()的内容,确定的行为
}
//这种方法仍然不是异常安全的,如果SpecialString已经被创建而对for_each的调用还没有开始时有异常被抛出,则会有资源泄漏发生
//解决这种方法是使用智能指针容器代替指针容器,这里的智能指针通常是指被引用计数的指针
//STL本身没有引用计数形式的智能指针,可以使用Boost库中的shared_ptr,则例子应该改写为:
void doSomthing()
{
typedef boost::shared_ptr<Widget> SPW;
vector<SPW> vwp;
for (int i = 0; i < size; ++i)
vwp.push_back(SPW(new Widget)); //从Widget*创建SPW,然后对它进行一次push_back,使用vwp;
}//这里不会有Widget资源泄露,即使在上面的代码中有异常抛出
永远不要错误的认为:你可以通过创建auto_ptr的容器使指针被自动删除
需要记住的是:STL容器很智能,但没有智能到知道是否该删除自己所包含的指针的程度。当你使用指针的容器时,而其中的指针应该被删除时,为了避免资源泄漏,
1、使用引用计数形式的智能指针对象(boost的shared_ptr)代替指针
2、当容器被析构时手工删除其中的每个指针
第8条:切勿创建包含auto_ptr的容器对象
auto_ptr的容器是被禁止的。
当你复制一个auto_ptr时,它所指向的对象的所有权被移交到拷入的auto_ptr上,而它自身被设置为NULL,可以理解为复制一个auto_ptr意味着改变它的值
例子:
auto_ptr<Widget> pw1(new Widget); //pw1指向一个Widget
auto_ptr<Widget> pw2(pw1) ; //pw2指向pw1的Widget:pw1被置为NULL(Widget的所有权从pw1转移到pw2上)
pw1 = pw2; //现在pw1又指向Widget了:pw2被置为NULL
//对该类的指针的排序准则
bool widgetAPCompare(const auto_ptr<Widget>&lhs,
const auto_ptr<Widget>&rhs)
{
return *lhs < *rhs; //假定对Widget存在operator<操作符
}
vector<auto_ptr<Widget> > widgets; //不应该
...
//这个操作是不对的,因为排序过程中,有赋值操作,会将widgets中的一个
//或多个auto_ptr可能被置为NULL,对vector进行排序可能会改变它的内容
//这可以从读sort的源码看出来
sort(widgets.begin(), widgets.end(), widgetAPCompare);
所以不要使用包含auto_ptr的容器