当前位置: 首页 > news >正文

条款9:利用destructors避免泄露资源

对指针说拜拜。承认吧,你从未真正喜欢过它,对不?

好,你不需要对所有指针说拜拜,但是你真的得对那些用来操控局部性资源(local resources)的指针说莎唷娜拉了。

举个例子,你正在为“小动物收养保护中心”(一个专门为小狗小猫寻找收养家庭的组织)编写一个软件。收养中心每天都会产生一个文件,其中有它所安排的当天收养个案。你的工作就是写一个程序,读这些文件,然后为每一个收养个案做适当处理。


合理的想法是定义一个抽象基类(abstract base class)ALA("Adorable Litle Animal"),再从中派生出针对小狗和小猫的具体类(concrete classes)。其中有个虚函数processAdoption,负责“因动物种类而异”的必要处理动作。

class ALA{
public:
virtual void processAdoption()= 0;
...
};class Puppy: public ALA
{
public:
virtual  void processAdoption();
...
};class Kitten: public ALA
{
public:
virtual void processAdoption ();
...
};

你需要一个函数,读取文件内容,并视文件内容产生一个Puppyobject或一个Kitten object。这个任务非常适合用virtual constructor完成,那是条款25中描述的一种函数。

对本目的而言,以下声明便是我们所需要的:

//从s读取动物信息,然后返回一个指针,指向一个
// 新分配的对象,有着适当的类型(Puppy或Kitten)
ALA * readALA(istream& s);

你的程序核心大约是一个类似这样的函数:

void processAdoptions(istream& dataSource)
{while (datasource)//如果还有数据,{ALA* pa = readALA(dataSource);//取出下一只动物,pa->processAdoption();//处理收养事宜,delete pa;//删除readALA返回的对象。}
}

这个函数走遍dataSource,处理它所获得的每一条信息。

唯一需要特别谨慎的是,它必须在每次迭代的最后,记得将pa删除。这是必要的,因为每当readALA被调用,便产生一个新的 heap object。如果没有调用 delete,这个循环很快便会出现资源泄漏的问题。

现在请考虑:如果pa->processAdoption抛出一个exception,会发生什么事情。processAdoptions 无法捕捉它,所以这个 exception 会传播到 processAdoptions的调用端。processAdoptions 函数内“位于pa->processAdoption 之后的所有语句”都会被跳过,不再执行,这也意味pa不会被删除。结果呢,只要pa->processAdoption 抛出一个 exception,processAdoptions 便发生一次资源泄漏。

要避免这一点,很简单:

void processAdoptions(istream& dataSource)
{while (dataSource){ALA* pa = readALA(dataSource),try {pa->processAdoption();}catch (...)//捕捉所有的exceptions。{delete pa;//当exception 被抛出,避免资源泄漏。throw;//将exception 传播给调用端。}delete pa;//如果没有exception被抛出,也要避免资源泄漏。}
}

但你的程序因而被try 语句块和catch 语句块搞得乱七八糟。

更重要的是,你被迫重复撰写其实可被正常路线和异常路线共享的清理代码(cleanup code)本例指的是delete动作。

这对程序的维护造成困扰,撰写时很烦人,感觉也不理想。不论我们是以正常方式或异常方式(抛出一个exception)离开processAdoptions函数,我们都需要删除pa,那么何不集中于一处做这件事情呢?

其实不必大费周章,只要我们能够将“一定得执行的清理代码”移到processAdoptions函数的某个局部对象的destructor 内即可。因为局部对象总是会在函数结束时被析构,不论函数如何结束(唯一例外是你调用longjmp而结束。longjmp 的这个缺点正是C++ 支持exceptions的最初的主要原因)。于是,我们真正感兴趣的是,如何把delete 动作从 processAdoptions 函数移到函数内某个局部对象的destructor内。

解决办法就是,以一个“类似指针的对象”取代指针pa,如此一来,当这个类似指针的对象被(自动)销毁,我们可以令其destructor 调用delete。

“行为类似指针(但动作更多)”的对象我们称为smart pointers。如条款28所言,你可以做出非常灵巧的“指针类似物”。本例倒是不需要什么特别高档的产品,我们只要求它在被销毁(由于即将离开其scope)之前删除它所指的对象,就可以啦。

技术上这并不困难,我们甚至不需要自己动手。C++标准程序库(见条款E49)提供了一个名为auto_ptr的class template,其行为正是我们所需要的。每个auto_ptr的constructor 都要求获得一个指向 heap object 的指针;其destructoy会将该heapobject 删除。

如果只显示这些基本功能,auto_ptr 看起来像这样:

template<class T>
class auto_ptr
{
public:auto_ptr(T* p = 0) :ptr(p) {}// 存储对象。~auto ptr(){delete ptr;// 删除对象。}private:T* ptr;//原始指针(指向对象).
};

auto_ptr 标准版远比上述复杂得多。上述这个剥掉一层皮的东西并不适合实际运用2(至少还需加上copy constructor,assignment operator 及条款28所讨论的指针仿真函数operator*和operator->),但是其背后的观念应该很清楚了:以auto_ptr 对象取代原始指针,就不需再担心heap objects没有被删除一一即使是在exceptions被抛出的情况下。

注意,由于auto ptr destructor 采用“单一对象”形式的delete,所以auto ptr 不适合取代(或说包装)数组对象的指针。如果你希望有一个类似 autoptr的template可用于数组身上,你得自己动手写一个。不过如果真是这样,或许更好的选择是以vector 取代数组。
以auto_ptr对象取代原始指针之后,processAdoptions 看起来像这样:

void processAdoptions(istream& dataSource)
{while (dataSource){auto_ptr<ALA> pa(readALA(dataSource)pa->processAdoption();}
}

这一版和原先版本的差异只有两处。

  • 第一,pa被声明为一个 auto_ptr<ALA>对象,不再是原始的 ALA*指针;
  • 第二,循环最后不再有delete 语句。就这样啦,其他每样东西都没变,除了析构动作外,auto_ptr 对象的行为和正常指针完全一样。很简单,不是吗?

隐藏在auto ptr背后的观念一—以一个对象存放“必须自动释放的资源”,并依赖该对象的destructor 释放一—亦可对“以指针为本”以外的资源施行。

考虑图形界面(GUT)应用软件中的某个函数,它必须产生一个窗口以显示某些信息:

//此函数可能会在抛出一个 exception 之后发生资源泄漏问题。
void displayInfo(const Informations info)
{WINDOW_HANDLE w(createwindow());displayinfo in window corresponding to w,destroyWindow(w);
}

许多窗口系统都有 C语言接口,运行诸如 createWindow 和 destroyWindow这类函数,取得或释放窗口(被视为一种资源)。如果在信息显示于的过程中发生exception,w所持有的那个窗口将会遗失,其他动态分配的任何资源也会遗失。

解决之道和先前一样,设计一个class,令其constructor 和destructor 分别取得资源和释放资源:

//这个class 用来取得及释放一个 window handle。
class WindowHandle {
public:WindowHandle(WINDOW HANDLE handle) :w(handle) {}~WindowHandle() {destroyWindow(w);}operator WINDOW_HANDLE(){return w;//详述于下。}
private:WINDOW_HANDLE w;//以下函数被声明为private,用以阻止产生多个 WINDOW HANDLE。//条款28讨论了一个更弹性的做法。WindowHandle(const WindowHandle&);WindowHandle& operator=(const WindowHandle&);
};

这看起来就像auto_ptr template 一样,只不过其赋值动作(assignment)和复制动作(copying)被明确禁止了(见条款E27)。

此外,它有一个隐式类型转换操作符,可用来将一个 windowHandle 转换为一个 WINDOW HANDLE。

这项能力对于WindowHandleobject 的实用性甚有必要,意味你可以像在任何地方正常使用原始的WINDOW_HANDLE一样使用一个 windowHandle(不过条款5也告诉你为什么应该特别小心隐式类型转换操作符)。
 


有了这个 WindowHandle class,我们可以重写 displayInfo如下:

// 此函数可以在exception 发生时避免出现资源泄漏问题。
void displayInfo(const Information& info)
{WindowHandle w(createwindow());display info in window corresponding to w;
}

现在即使 displayInfo函数内抛出 exception,createWindow 所产生的窗口还是会被销毁。

只要坚持这个规则,把资源封装在对象内,通常便可以在exceptions 出现时避免泄漏资源。

但如果exception 是在你正取得资源的过程中抛出的,例如在一个“正在抓取资源”的class constructor 内,会发生什么事呢?

如果exception 是在此类资源的自动析构过程中抛出的,又会发生什么事呢?此情况下constructors和destructors 是否需要特殊设计?

是的,它们需要,你可以在条款 10和条款 11中学到这些技术。
 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 上周暗网0day售卖情报一览
  • 【管理咨询宝藏114】贝恩为某知名化妆品战略规划方案
  • 低代码与人工智能:革新智能客服系统的高效之道
  • 使用vanna实现Text2SQL
  • wordpress主题给网站增加一个版权声明区块代码分享
  • 品牌曝光秘籍:让更多人知道你的品牌,这些推广技巧必学
  • 软件设计师中级 重点 笔记
  • 【从零开始学习RabbitMQ | 第一篇】如何确保生产者的可靠性
  • 信息抽取模型TPLinker
  • 入门四认识HTML
  • JAVA面试题大全(十六)
  • Python Requests库中data与json参数的区别
  • protobuf —— 快速上手
  • 算法课程笔记——素数朴素判定埃氏筛法
  • 队列(从数据结构的三要素出发)
  • @jsonView过滤属性
  • 【5+】跨webview多页面 触发事件(二)
  • canvas绘制圆角头像
  • Fastjson的基本使用方法大全
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • Javascript基础之Array数组API
  • javascript面向对象之创建对象
  • Python_网络编程
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • spring学习第二天
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • vue数据传递--我有特殊的实现技巧
  • webpack入门学习手记(二)
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 京东美团研发面经
  • 警报:线上事故之CountDownLatch的威力
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 如何设计一个比特币钱包服务
  • 入门级的git使用指北
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 终端用户监控:真实用户监控还是模拟监控?
  • 走向全栈之MongoDB的使用
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 整理一些计算机基础知识!
  • ​520就是要宠粉,你的心头书我买单
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (1)Nginx简介和安装教程
  • (21)起落架/可伸缩相机支架
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (不用互三)AI绘画:科技赋能艺术的崭新时代
  • (九十四)函数和二维数组
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (图)IntelliTrace Tools 跟踪云端程序
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .Family_物联网
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存