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

news2024/9/25 15:29:38

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

好,你不需要对所有指针说拜拜,但是你真的得对那些用来操控局部性资源(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.coloradmin.cn/o/1705911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux漏洞SSL/TLS协议信息泄露漏洞(CVE-2016-2183) - 非常危险(7.5分) 解决办法!升级openssl

漏洞情况 详细描述 TLS是安全传输层协议&#xff0c;用于在两个通信应用程序之间提供保密性和数据完整性。 TLS, SSH, IPSec协商及其他产品中使用的IDEA、DES及Triple DES密码或者3DES及 Triple 3DES存在大约四十亿块的生日界&#xff0c;这可使远程攻击者通过Sweet32攻击&…

不聚焦情绪,不精神内耗:成长的自我修炼

在我们的人生旅途中&#xff0c;总会遇到各种各样的困境和挑战。如何在逆境中保持积极的心态&#xff0c;专注于个人成长&#xff0c;是每一个人都需要面对和思考的问题。这篇文章将探讨如何不抱怨、不指责、不聚焦情绪、不精神内耗&#xff0c;专注于解决困境和个人成长。 问…

基于 IP 的 DDOS 攻击实验

介绍 基于IP的分布式拒绝服务&#xff08;Distributed Denial of Service, DDoS&#xff09;攻击是一种利用大量受控设备&#xff08;通常是僵尸网络&#xff09;向目标系统发送大量请求或数据包&#xff0c;以耗尽目标系统的资源&#xff0c;导致其无法正常提供服务的攻击方式…

swiftui基础组件Image加载图片,以及记载gif动图示例

想要在swiftui中展示图片&#xff0c;可以使用Image这个组件&#xff0c;这个组件可以加载本地图片和网络图片&#xff0c;也可以调整图片大小等设置。先大概看一下Image的方法有哪些可以用。 常用的Image属性 1.调整图像尺寸&#xff1a; 使用 resizable() 方法使图像可调整…

100个 Unity小游戏系列七 -Unity 抽奖游戏专题五 刮刮乐游戏

一、演示效果 二、知识点讲解 2.1 布局 void CreateItems(){var rewardLists LuckyManager.Instance.CalculateRewardId(rewardDatas, Random.Range(4, 5));reward_data_list reward_data_list ?? new List<RewardData>();reward_data_list.Clear();for (int i 0; …

知攻善防应急响应靶机训练-Web3

前言 本次应急响应靶机采用的是知攻善防实验室的Web-3应急响应靶机 靶机下载地址为&#xff1a; https://pan.quark.cn/s/4b6dffd0c51a 相关账户密码 用户:administrator 密码:xj123456xj123456 解题过程 第一题-攻击者的两个IP地址 直接查看apache的log日志搜索.php 发现…

AI助力科研:自动化科学构思生成系统初探

科学研究作为推动创新和知识进步的关键活动&#xff0c;在解决复杂问题和提升人类生活水平方面发挥着至关重要的作用。然而&#xff0c;科学研究的固有复杂性、缓慢的进展速度以及对专业专家的需求&#xff0c;限制了其生产力的提升。为了增强科研效率&#xff0c;本文提出了一…

12.Redis之补充类型渐进式遍历

1.stream 官方文档的意思, 就是 stream 类型就可以用来模拟实现这种事件传播的机制~~stream 就是一个队列(阻塞队列)redis 作为一个消息队列的重要支撑属于是 List blpop/brpop 升级版本.用于做消息队列 2.geospatial 用来存储坐标 (经纬度)存储一些点之后,就可以让用户给定…

据阿谱尔APO Research调研显示,2023年全球热喷涂涂料市场销售额约为110.37亿美元

根据阿谱尔 (APO Research&#xff09;的统计及预测&#xff0c;2023年全球热喷涂涂料市场销售额约为110.37亿美元&#xff0c;预计在2024-2030年预测期内将以超过4.82%的CAGR&#xff08;年复合增长率&#xff09;增长。 热喷涂涂层是指将熔融或加热的金属、合金或陶瓷等材料喷…

【数据结构】P1 数据结构是什么、算法怎样度量

1.1 基本概念与术语 数据&#xff1a; 数据是信息的载体&#xff0c;是所有能被计算机识别以及处理的符号。数据元素&#xff1a; 数据元素是数据基本单位&#xff0c;由若干 数据项 组成&#xff0c;数据项是构成数据元素最小的单位。 e . g . e.g. e.g. 数据元素如一条学生记…

ShardingSphere使用案例

文章目录 一、分表1. 项目架构搭建2. 数据库搭建3. 案例开发一、分库1. 创建新的库2. 修改配置文件一、分表 1. 项目架构搭建 创建Maven项目导入相关依赖<dependencies><

ArcgisPro3.1.5安装手册

ArcgisPro3.1.5安装手册 一、目录介绍: 二、安装教程&#xff1a; (1)安装顺序&#xff1a;最先安装运行环境&#xff08;runtime6.0.5&#xff09;,接着安装install里面的文件&#xff0c;最后复制path里面的文件替换到软件bin文件夹下即可。 (2)具体安装步骤&#xff…

蓝桥杯练习系统(算法训练)ALGO-932 低阶行列式计算

资源限制 内存限制&#xff1a;64.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给出一个n阶行列式(1<n<9)&#xff0c;求出它的值。 输入格式 第一行给出两个正整数n,p&#xff1b;   接下来n行&…

MySQL触发器实战:自动执行的秘密

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 MySQL触发器实战&#xff1a;自动执行的秘密 前言触发器的定义和作用触发器的定义和作用触发器的…

拌合楼系统开发(二十)解决海康DS-TVL224系列屏幕显示二维码思路

前言&#xff1a; 需求是想在通过程序动态控制显示屏显示二维码&#xff0c;最开始有些担心led这种点阵屏会不会对二维码显示出来后无法识别&#xff0c;实际测时候发现是没问题的。对于显示文字和语音播报&#xff0c;csdn上已经有大神有完整的代码。 海康威视道闸进出口LED屏…

linux开发之设备树

设备树的基本概念 1.什么是设备树?为什么叫设备树呢? 设备树是描述硬件的文本文件&#xff0c;因为语法结构像树一样。所以叫设备树。 2.基本名词解释 <1>DT:Device Tree //设备树 <2>FDT:Flattened Device Tree //开放设备树&#xff0c;起源于0penFirmware(0F…

KNN算法 比较

文章目录 PreHufuOne RoundMulti Round Pre 安全操作参考链接 Hufu hufu算法详细信息。Alg.1 示出了对联合kNN查询的分解。line 1-8得出半径。我们初始化半径的下界&#xff08;l0&#xff09;和上界&#xff08;uv0&#xff09;&#xff0c;其中v0可以设置为区域的直径或由用…

git中忽略文件的配置

git中忽略文件的配置 一、在项目根目录下创建.gitignore文件二、配置规则如果在配置之前已经提交过文件了&#xff0c;要删除提交过的&#xff0c;如何修改&#xff0c;参考下面的 一、在项目根目录下创建.gitignore文件 .DS_Store node_modules/ /dist# local env files .env…

一机实现All in one,NAS如何玩转虚拟机!

常言道&#xff0c;中年男人玩具有三宝 充电器、路由器、NAS 你问我NAS的魔力在哪里&#xff1f; 一机实现All in one洒洒水啦 那NAS又如何玩转虚拟机呢? 跟我来 0基础也能轻松get! NAS如何玩转虚拟机 铁威马NAS的VirtualBox的简单易用&#xff0c;可虚拟的系统包括Win…

C++编程函数中switch实例用法

switch语法 switch (func_cb.sta) switch后续跟随多个成对的case和break&#xff0c;分别包含if/endif判断语句 每个 case 后跟一个要比较的值和一个冒号&#xff0c;当被测试的变量等于 case 中的常量时&#xff0c;case下一行的语句将被执行 switch 语句可以嵌套。 嵌套时&am…