STL

《Effective STL》

7《如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉》

Posted by Liangjf on April 9, 2019

条目七《如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉》

在STL中容器是智能的,可以在容器销毁时自动调用容器里对象的析构函数来销毁容器存储的对象。

STL的容器虽然比较智能,但是没有智能到可以自动销毁new出来的指针对象。

所以在使用STL中的容器时,如果保存的是mew出来的对象的指针。如果在容器销毁之前没有把new出来的对象释放,会造成内存泄露。

解决方法版本一:

void doSomething()
{
    vector<Widget*> vwp;
    ...
    for (vector<Widget*>::iterator i = vwp.begin();i != vwp.end(),++i)
    {
    	delete *i;
    }
}

这种手工释放容器里new的对象不太可靠,菲异常安全的。如果在delete的时候爆出异常,那么还是会引起内存泄露。

解决方法二:

template<typename T>
struct DeleteObject : public unary_function<const T*, void>
{
    void operator()(const T* ptr) const
    {
    	delete ptr;
    }
}; 这是一个函数对象。

void doSomething()
{
    ... // 同上
    for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);
} 手动销毁new出来的对象

使用函数对象的方式,可以保证对象的安全销毁完,虽然在调用时需要指明函数对象类型,实现了适配器作用,但是这种方法需要公有继承,有个特殊情况,就是STL的string。

class SpecialString: public string { ... };
void doSomething()
{
    deque<SpecialString*> dssp;
    ...
    for_each(dssp.begin(), dssp.end(),DeleteObject<string>());// 虚析构函数的基类
} string是没有虚析构函数的,所以在作为基类时,在这个对象使用完析构时,会找不到对应的析构函数,这时的行为是未知的。

解决方法三:

struct DeleteObject {
    template<typename T> // 模板化加在这里
    void operator()(const T* ptr) const
    {
        delete ptr;
    }
}

void doSomething()
{
    deque<SpecialString*> dssp;
    for_each(dssp.begin(), dssp.end(),DeleteObject());
} 把模板化放在类里面,重载()操作符前,由编译器自动判断调用者的类型,这样就可以避免公有继承这一步操作,也就实现了通用。但是却丢弃了**可适配**的能力。

最优的解决方法:

void doSomething()
{
    typedef boost::shared_ ptr<Widget> SPW;
    vector<SPW> vwp;
    for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
    vwp.push_back(SPW(new Widget)); // 从一个Widget建立SPW,然后进行一次push_back
    ...
    // 使用vwp
} 使用boost里的shared_ptr智能指针,通过其的自动计算引用计数的实现来,在使用STL容器保存new出来的对象指针时,超出作用域范围自动销毁智能指针里的对象,实现安全可靠高效的解决内存泄露方案。

总结

总的来说,在使用STL容器存储new出来的对象的指针时,需要注意对象的析构销毁步骤。这一步是需要我们自己来管理的。