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

embedded/2024/9/25 23:17:52/

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

好,你不需要对所有指针说拜拜,但是你真的得对那些用来操控局部性资源(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中学到这些技术。
 


http://www.ppmy.cn/embedded/43280.html

相关文章

CS西电高悦计网课设——校园网设计

校园网设计 一&#xff0c;需求分析 所有主机可以访问外网 主机可以通过域名访问Web服务器 为网络配置静态或者动态路由 图书馆主机通过DHCP自动获取IP参数 为办公楼划分VLAN 为所有设备分配合适的IP地址和子网掩码&#xff0c;IP地址的第二个字节使用学号的后两位。 二…

前端开发工程师——AngularJS

一.表达式和语句 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-w…

央视网视频下载和花屏问题处理

央视网(www.cctv.com)视频下载往往是花屏的&#xff0c;如何处理呢&#xff1f; 如果您是IT技术开发者&#xff0c;那么您可以通过下面步骤自己实现。 用chrome浏览器&#xff0c;F2打开开发者工具&#xff0c;找到当前页面的network 然后找一个接口&#xff1a;https://vdn.a…

57. UE5 RPG 处理AI敌人转向以及拾取物品的问题

在上一篇文章中&#xff0c;我们实现了使用AI行为树控制敌人进行移动&#xff0c;它们可以一直跟随玩家&#xff0c;虽然现在还未实现攻击。但在移动过程中&#xff0c;我发现了有两个问题&#xff0c;第一个是敌人转向的时候很僵硬&#xff0c;可以说是瞬间转向的&#xff0c;…

Git原理及常用命令小结——实用版(ing......)、Git设置用户名邮箱

Git基本认识 Git把数据看作是对小型文件系统的一组快照&#xff0c;每次提交更新&#xff0c;或在Git中保存项目状态时&#xff0c;Git主要对当时的全部文件制作一个快照并保存这个快照的索引。同时&#xff0c;为了提高效率&#xff0c;如果文件没有被修改&#xff0c;Git不再…

【软考高项】- 2024.05.25 第一批考情介绍

一、选择题 1、信息化融合包含内容&#xff0c;产品&#xff0c;产业&#xff0c;等。 2、it内部审计 3、排列活动的数据表现工具 4、费用现值法 5、数据安全法 6、专利&#xff0c;外观设计&#xff0c;实用新型 7、有好几个过程定义和作用。 8、甲乙两公司&#xff0…

Java 8 HashMap源码解析

一、引言 HashMap是Java集合框架中最重要的类之一&#xff0c;它基于哈希表实现&#xff0c;提供了快速的插入和查找操作。在Java 8中&#xff0c;HashMap得到了一些改进&#xff0c;包括引入了红黑树&#xff08;在链表长度超过一定阈值时&#xff09;来优化查找和插入性能。…

香橙派AI Pro测评--ROS及激光SLAM

文章目录 前言一、外形与质感二、软件测评1. 系统界面2. ROS安装3. ROS节点测试4. SLAM算法测试 总结 前言 今天刚收到了官方寄来的“香橙派 AI Pro”开发板&#xff0c;这篇文章将会对香橙派 AI Pro的外形、质感、运行速度进行一个测评&#xff0c;然后我会在这个开发板上安装…