Effective STL第7条:容器之(如果容器内元素通过new创建,切记在容器对象析构前将指针delete掉(使用智能指针))
发布日期:2021-06-29 22:33:13 浏览次数:2 分类:技术文章

本文共 3243 字,大约阅读时间需要 10 分钟。

一、容器的析构

  • STL的容器相当聪明。当它们自身被析构时,它们会自动析构容器内所包含的每个对象

二、通过new操作符申请的元素导致的内存泄漏

  • 但是,如果容器内的元素是通过new创建的,那么在释放容器的时候,指针的“析构函数”不会做任何事情,因此new所创建的内存就不会释放掉。如果不手动delete掉,那么就会造成内存泄漏

演示说明

  • 下面vector容器内的每个元素通过new操作符创建
void doSomething(){    vector
vwp; for (int i = 0; i < SOME_MAGIC_NUMBER; ++i) vwp.push_back(new Widget); //通过new创建容器内的每个元素} //函数执行结束
  • 函数结束之后,vector容器会析构,容器析构的时候,容器内所有的元素也会发生析构,但是new操作符创建的对象其“析构”不会发生任何事情,因此也不会delete,所以就产生了内存泄漏

三、通过delete显式释放容器元素

  • 上面我们通过new操作符创建容器元素,为了防止内存泄漏,最简单的方法就是通过delete释放防止内存泄漏
  • 见下面代码:
void doSomething(){    vector
vwp; for (int i = 0; i < SOME_MAGIC_NUMBER; ++i) vwp.push_back(new Widget); /*vwp指向相应的操作*/ //循环释放每个元素 for (auto i = vwp.begin(); i != vwp.end(); ++i) delete *i; }

但是这种方式仍存在两个问题:

  • 问题①:for循环可以做的事情,可以改用for_each来实现,因为for_each表达意思的更清楚(见第43条)。在下面我们也会使用for_each来实现这个delete操作
  • 问题②:这段代码不是异常安全的。如果在new操作和delete操作之间程序抛出了异常导致程序终止,那么delete语句将永远不会执行,同样也会产生内存泄漏

四、使用函数对象来封装delete并应用于for_each

  • 上述我们使用delete来释放对象,但是for最好改用为for_each,使用for_each的时候需要把delete封装为一个函数对象
  • 例如:下面将delet操作等装载DeleteObject函数对象中,并在程序中通过for_each来调用
//封装一个函数对象template
struct DeleteObject :public unary_function
//第40条解释了为什么有这个继承{ void operator()(const T* ptr)const { delete ptr; }};void doSomething(){ vector
vwp; for (int i = 0; i < SOME_MAGIC_NUMBER; ++i) vwp.push_back(new Widget); for_each(vwp.begin(), vwp.end(), DeleteObject
()); //使用for_each释放}

但是此种方法存在一个问题

  • 该方法存在一个问题就是每次调用DeleteObject函数的时候需要传入容器内元素的类型(在这里是Widget)。这种方法是比较多余的,因为我们当然知道自己要删除的容器元素类型。并且在某些时候会产生一些很难追踪的错误,请看下面案例
  • 例如:标准的STL容器都没有虚析构函数,所以string也没有。下面我们定义了一个类继承于string,并且将其指针类型作为deque的容器元素类型,但是我们调用DeleleteObject的时候,却通过基类的指针删除派生类对象,而基类string又没有虚析构函数,因此会产生错误
template
struct DeleteObject :public unary_function
//第40条解释了为什么有这个继承{ void operator()(const T* ptr)const { delete ptr; }};//继承于stringclass SpecialString :public string{};void doSomething(){ deque
dssp; //dssp do somethins //不确定的行为:通过基类的指针删除派生类对象,而基类string又没有虚析构函数 for_each (dssp.begin(), dssp.end(), DeleteObject
());}
  • 对于这种现象,我们在下面介绍使用无类型的函数对象来解决 

五、设计无类型的函数对象

  • 在上面,我们在调用自己封装的函数对象时,需要传入元素的类型,并且还可能会产生错误。在此,我们改写函数,让编译器自动推断出传给DeleteObject::operator()的指针的类型,我们要做的就是将模板化移到到operator()中
  • 见下面代码:
//封装一个函数对象(此处取消了模板化和继承)struct DeleteObject{    template
void operator()(const T* ptr)const { delete ptr; }};class SpecialString :public string{};void doSomething(){ deque
dssp; //dssp do somethins for_each (dssp.begin(), dssp.end(), DeleteObject());//自动推断类型}

六、异常安全

  • 上面的delete与函数对象的方法虽然都可以解决内存泄漏的问题,但是还有异常安全这个问题没有解决
  • 异常安全的问题的解决方法就是:使用智能指针来替代指针容器

七、使用智能指针

  • 智能指针:C++的智能指针通过引用计数的形式来保证内存不会泄露以及保证其他在各个方面的安全
  • 至于智能指针不是本文讨论的重点,智能指针可以参阅以下几篇文章:
    • 智能指针share_ptr:
    • 智能指针unique_ptr:
    • 智能指针weak_ptr:
  • 演示案例:
void doSomething(){	typedef boost::share_ptr
SPW; vector
vwp; for (int i = 0; i < SOME_MAGIC_NUMBER; ++i) vwp.push_back(SPW(new Widget));}//函数结束也不需要手动释放,智能指针会自动释放内存

八、为什么使用智能指针而不是使用函数对象

  • 在上面我们封装了DeleteObject函数对象来释放内存,那么为什么我们不自己定义一个类似的DeleteArray结构,并将该结构应用于元素为指向数组的指针的容器,从而避免内存泄漏。这当然是可能的,但是不可取。第13条将会解释为什么动态分配的数组几乎总不如vector和string对象

转载地址:https://dongshao.blog.csdn.net/article/details/104032452 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:TCP/IP卷一:66---TCP连接管理之(TCP选项(最大段大小/选择确认/窗口缩放/时间戳/用户超时/认证选项))
下一篇:MySQL数据类型——字符串类型(CHAR、BINARY、BLOB、TEXT、ENUM、SET)

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月16日 19时12分03秒