`
buliedian
  • 浏览: 1243810 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

ZT:对象生死劫 - 构造函数和析构函数的异常

阅读更多

转自:http://blog.csdn.net/leadzen/archive/2007/09/13/1783116.aspx

原作者:李战

构造函数和析构函数分别管理对象的建立和释放,负责对象的诞生和死亡的过程。当一个对象诞生时,构造函数负责创建并初始化对象的内部环境,包括分配内存、创建内部对象和打开相关的外部资源,等等。而当对象死亡时,析构函数负责关闭资源、释放内部的对象和已分配的内存。

  在对象生死攸关的地方,如果程序代码出现问题,常常会发生内存泄漏,从而产生可能危害系统运行的孤魂野鬼。大量的事实表明,业务逻辑代码写得非常严谨的程序在运行中仍然发现存在内存泄露,大都是构造和析构部分的代码存在问题。

  而许多程序员都习惯于面向对象的编程,需要时就建立一个对象,不用时就将其释放。这样的习惯简化了我们的思路,正是面向对象编程思想带来的好处。也许有于太习惯,很多程序员都忽略了在对象生死的瞬间可能产生的问题,这却值得我们去认真反思。

  其实,对象生死间的异常问题是一个充满争议的问题。甚至不同的编程语言,在对象生死间的异常问题上也持不同的态度。

  C++语言说:一个对象在出生的过程中出现问题,那这个对象就是一个没有生命的怪胎。既然它不是一个完整的对象,就根本不存在析构或释放的说法。因此,C++在调用构造函数时产生异常,是不会调用对象的析构函数的,而仅仅释放对象的内存空间,之后就把异常抛给程序员处理。

  DELPHI (Object Pascal)语言认为:对象虽然在出生过程中出现异常,但它已经具有部分生命。既然是有生命的东西,都应该有死亡的权利。因此,DELPHI在调用构造函数时产生异常,一定会先析构函数,然后再抛出异常。

  那么,谁的观点对?谁的观点对?

  我想,这个问题争论上九九八十一天也未必有结果。因此,我们不必纠缠于观点的争论。只要我们知道了不同编程程语言有不同的处理方法就够了,当我们用哪种语言来编程时就尊重该种语言的观点,这才是务实的程序员应该做的!

  对于C++语言来说,由于构造函数产生异常时不会调用对应的析构函数,那么在构造函数里发生异常前的代码所创建的其他东西就不能被析构函数内的相关释放代码所释放。例如:

class TMyObject
{
private:
TOtherObject * OtherObject;
public:
TMyObject()
{
OtherObject = new TOtherObject();

...... //这后面的代码发生异常将导致OtherObject不会被释放!

}
~TMyObject()
{
......

delete OtherObject; //构造函数发生异常时析构函数根本不会被调用,此代码也不会被执行!
}
}


  回想一下自己的程序中是否也存在类似的代码?如果是这样,那可就要注意了,这样的代码在C++中是不安全的!

  那么,应该怎样写才安全呢?

  事实上,如果在C++的构造函数里创建了其他东西,你就必须考虑构造函数发生异常的情况。在构造函数中发生异常时,已经创建的东西必须被释放掉,然后再重新抛出异常给上层调用代码处理,这才是C++构造函数中正确的异常处理方法。因此,前面的构造函数应该改写成下面的形式:

TMyObject()
{
OtherObject = new TOtherObject();
try
{
...... //这里的代码发生异常。
}
catch(...)
{
delete OtherObject; //确保发生异常时,能释放掉已建立的东西。
throw; //再次抛出异常给上层调用代码处理。
};
}

  如果,一个构造函数要创建很多其他东西的话,就应该编写相应的try try try ... catch catch catch形式的嵌套代码(或者相同逻辑的代码)来确保构造函数的正确性。

  不过,还有另外一种不用try...catch来确保构造异常时正确释放内存的方法,那就是C++标准类库中的那个著名的auto_ptr模板类。auto_ptr又常常被称为智能指针,它巧妙地利用C++退出作用域时会自动释放变量的机制,来清理其维护的对象。再看改写的代码:

#include <std.h>

class TMyObject
{
private:
std::auto_ptr<TOtherObject> OtherObject;
public:
TMyObject()
{
OtherObject = new TOtherObject();

...... //这后面的代码发生异常,也可以确保OtherObject会被自动释放!

}
~TMyObject()
{
//一旦将对象交给auto_ptr来维护,就永远不要自己释放该对象。因此,这里什么都不用写。
}
}

  这样的代码那么简洁而且有效,也便于阅读和维护。我曾经也认为这里的OtherObject是类的成员,并不在构造函数的作用域内,因此异常跳出构造函数时并不会导致auto_ptr的析构函数被调用。但从创建对象的机制上来说,C++会先分配对象空间和创建成员对象,然后才调用构造函数的。一旦构造函数发生异常,C++一定会释放成员对象和空间,以丢弃这个不完整对象,然后才抛出异常。从这个层次上讲,auto_ptr成员的析构函数也会被正确调用。

  不过,atuo_ptr在使用过程中也有些副作用。比如,你把一个auto_ptr赋值给另一个auto_ptr,前一个auto_ptr就会变成null值,这不符合正常的赋值语义。这是由于auto_ptr重载了赋值操作符的缘故,不懂auto_ptr实现原理的人就常常犯null指针错误。使用auto_ptr还有一句名言:“别把一个对象赋给两个auo_ptr变量”,因为这会导致两次释放该对象。不管怎样,如果嫌try...catch麻烦,使用auto_ptr来保证不发生内存泄漏也是一个非常不错的选择。

  相比之下,DELPHI语言处理构造函数的异常就简单多了。因为DELPHI保证在构造函数发生异常后,会调用析构函数。不过并非所有的析构函数都满足这一条件,只有Destroy可以,而且它是一个虚函数。

TMyObject = class
private
OtherObject: TOtherObject;
public
constructor Create;
destructor Destory; override;
end;

constructor TMyObject.Create;
begin
OtherObject := TOtherObject.Create;

...... //这后面的代码发生异常可以保证析构函数Destory被调用,从而释放OtherObject!

end;

destructor TMyObject.Destory;
begin
......
OtherObject.Free; //这里OtherObject将被正确释放!

end;

  因为DELPHI的“构造异常时确保析构的机制”是非常基础的代码,它只能给根类TObject设计一个虚析构函数Destroy来作为析构函数调用。也就是说,如果你自己写的析构函数不是从Destory重载的,“构造异常时确保析构的机制”将失效!

  同时,DELPHI在根类TObject中提供了Free方法来方便对象释放。这个Free方法保证即使对象并为被创建(对象指针为nil),调用此方法也不会出错。这样,构造函数里面创建语句就可以很简洁地与析构函数的释放语句对应起来,方便我们看代码。看来,设计这个Free方法还是用心良苦啊!

  那么关于析构函数中的异常又会怎样呢?

  对象在死亡的过程中发生异常又引出一个有趣的问题,“想死死不了”或者“死了一半又不能死了”!那么,这个对象到底是死了还活着。这种既死又活的对象,就像量子理论中的那只“薛定谔的猫”一样有趣。的确存在,却难以琢磨!

  为此,C++根本不去纠缠这种复杂的问题,而是采用最简单的办法:如果析构函数抛出异常,将直接导致当前执行线程异常终止!如果是主线程中发生析构异常,程序立即退出!

  C++的这一做法是可以理解的,当代码已经走进无法想通的死胡同,对象只能疯掉,从而毁灭整个程序世界!看来并非芸芸众生才有无法逃脱的苦海,其实程序也有解不开的心结!

  所以,“永远不要在析构函数中抛出异常”成了编写C++代码的一条铁律!

  而在DELPHI中,析构函数发生异常和其他代码发生异常没有什么不同,发生异常之后的代码将不会被执行(finally部分的除外),异常将由能找到的上层异常处理代码来处理!如果异常最终没有被处理,才导致当前线程终止。

  我们来看下面的代码

destructor TMyObject.Destory;
begin
C.Free;
B.Free; //释放B发生异常,将导致后面的代码不会被执行,A不会被释放!
A.Free;
end;

  因此,严格地说,析构函数应该写成下面的形式:

destructor TMyObject.Destory;
begin
try
C.Free;
finally
try
B.Free; //这里发生异常,也可以强行释放A
finally
A.Free;
end;
end;
end;

  但这样的代码感觉怪怪的。而且,虽然释放B时发生异常也可以确保释放后面的A,但B到底是释放了还是没释放?是不是也会有内存泄漏呢?这就要看B的析构函数是怎么写的,问题将一直追溯下去。照这样下去,那些try...fianlly仅仅能保证尽可能释放对象,而根本没法保证不发生内存泄漏。

  看来这个析构函数异常不是一时半会能想通的问题,DELPHI的方法也并不比C++高明。因此,“永远不要在析构函数中抛出异常”依然可以作为一个简单的定律!

  既然能保证编写的每一个析构函数都不发生异常,那么那些try...finally也就没有必要了。

  正如一首歌唱的那样:虽然这世界有太多不如意,但是我们的程序依久要继续...

分享到:
评论

相关推荐

    YOLO算法-数据集数据集-330张图像带标签-椅子-书桌.zip

    YOLO系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中,文件名末尾是部分类别名称; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值; 【注】可以下拉页面,在资源详情处查看标签具体内容;

    java毕设项目之ssm蜀都天香酒楼的网站设计与实现+jsp(完整前后端+说明文档+mysql+lw).zip

    项目包含完整前后端源码和数据库文件 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/idea Maven包:Maven3.3 服务器:tomcat7

    weixin138社区互助养老+ssm(论文+源码)-kaic.zip

    weixin138社区互助养老+ssm(论文+源码)_kaic.zip

    光纤到户及通信基础设施报装申请表.docx

    光纤到户及通信基础设施报装申请表.docx

    java毕设项目之ssm基于jsp的精品酒销售管理系统+jsp(完整前后端+说明文档+mysql+lw).zip

    项目包含完整前后端源码和数据库文件 环境说明: 开发语言:Java 框架:ssm,mybatis JDK版本:JDK1.8 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/idea Maven包:Maven3.3 服务器:tomcat7

    功能完善的电商数据智能爬虫采集系统项目全套技术资料.zip

    功能完善的电商数据智能爬虫采集系统项目全套技术资料.zip

    YOLO算法-刀数据集-198张图像带标签-刀-枪.zip

    YOLO系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中,文件名末尾是部分类别名称; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值; 【注】可以下拉页面,在资源详情处查看标签具体内容;

    Android程序开发初级教程WORD文档doc格式最新版本

    ### Android程序开发初级教程(一):初识Android **平台概述** Google推出的Android操作系统平台已经正式亮相,这是一个基于Linux内核的开源操作系统。对于开发者而言,了解其架构和支持的开发语言至关重要。以下是Android平台的架构概览: **平台架构及功能** 1. **应用框架(Application Framework)**:包含可重用和可替换的组件,确保所有软件在该层面上的平等性。 2. **Dalvik虚拟机(Dalvik Virtual Machine)**:一个基于Linux的虚拟机,为Android应用提供运行环境。 3. **集成浏览器(Integrated Browser)**:基于开源WebKit引擎的浏览器,位于应用层。 4. **优化图形(Optimized Graphics)**:包括自定义的2D图形库和遵循OpenGL ES 1.0标准的3D实现。 5. **SQLite数据库**:用于数据存储。 6. **多媒体支持(Media Support)**:支持通用音频、视频以及多种图片格式(如MPEG4, H.264

    【组合数学答案】组合数学-苏大李凡长版-课后习题答案

    内容概要:本文档是《组合数学答案-网络流传版.pdf》的内容,主要包含了排列组合的基础知识以及一些经典的组合数学题目。这些题目涵盖了从排列数计算、二项式定理的应用到容斥原理的实际应用等方面。通过对这些题目的解析,帮助读者加深对组合数学概念和技巧的理解。 适用人群:适合初学者和有一定基础的学习者。 使用场景及目标:可以在学习组合数学课程时作为练习题参考,也可以在复习考试或准备竞赛时使用,目的是提高解决组合数学问题的能力。 其他说明:文档中的题目覆盖了组合数学的基本知识点,适合逐步深入学习。每个题目都有详细的解答步骤,有助于读者掌握解题思路和方法。

    .net core mvc在线考试系统asp.net考试系统源码考试管理系统 主要技术: 基于.net core mvc架构和sql server数据库,数据库访问采用EF core code fir

    .net core mvc在线考试系统asp.net考试系统源码考试管理系统 主要技术: 基于.net core mvc架构和sql server数据库,数据库访问采用EF core code first,前端采用vue.js和bootstrap。 功能模块: 系统包括前台和后台两个部分,分三种角色登录。 管理员登录后台,拥有科目管理,题库管理,考试管理,成绩管理,用户管理等功能。 教师登录后台,可进行题库管理,考试管理和成绩管理。 用户登录前台,可查看考试列表,参加考试,查看已考试的结果,修改密码等。 系统实现了国际化,支持中英两种语言。 源码打包: 包含全套源码,数据库文件,需求分析和代码说明文档。 运行环境: 运行需vs2019或者以上版本,sql server2012或者以上版本。

    YOLO算法-易拉罐识别数据集-512张图像带标签-可口可乐.zip

    YOLO系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中,文件名末尾是部分类别名称; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值; 【注】可以下拉页面,在资源详情处查看标签具体内容;

    (175415460)基于SpringBoot的通用管理系统源码+数据库+项目文档,前后端分离的通用管理系统模版,可用于开发毕业设计

    包含了登陆注册、用户管理、部门管理、文件管理、权限管理、日志管理、个人中心、数据字典和代码生成这九个功能模块 系统采用了基于角色的访问控制,角色和菜单关联,一个角色可以配置多个菜单权限;然后再将用户和角色关联,一位用户可以赋予多个角色。这样用户就可以根据角色拿到该有的菜单权限,更方便管理者进行权限管控。 本系统还封装了文件管理功能,在其他模块如若要实现图片/文件上传预览时,前端只需导入现成的 Vue 组件即可实现(使用 viewerjs 依赖实现),后端只需定义 String 类型的实体类变量即可,无需再去研究文件上传预览的相关功能,简化了开发者的工作量。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。

    三相10Kw光伏并网逆变器 包含全套理图 PCB 源代码

    三相10Kw光伏并网逆变器。包含全套理图 PCB 源代码

    GJB 5236-2004 军用软件质量度量

    GJB 5236-2004 军用软件质量度量文档,本称准规定了车用软件产品的质重模型和基本的度量。本标准为确定车用软件质量需求和衡量军用 软件产品的能力提供了一个框架。

    (179941432)基于MATLAB车牌识别系统【GUI含界面】.zip

    基于MATLAB车牌识别系统【GUI含界面】.zip。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。

    (9546452)宿舍管理系统

    【宿舍管理系统】是一种专为高校或住宿机构设计的信息化解决方案,旨在提高宿舍管理的效率和准确性。该系统包含了多项核心功能,如宿舍管理员管理、宿舍信息维护、查询、卫生检查以及电费缴纳等,旨在实现全面的宿舍运营自动化。 **宿舍管理员管理**功能允许指定的管理员进行用户权限分配和角色设定。这包括对管理员账户的创建、修改和删除,以及设置不同的操作权限,例如只读、编辑或管理员权限。通过这样的权限控制,可以确保数据的安全性和管理的规范性。 **宿舍添加与管理**是系统的基础模块。管理员可以录入宿舍的基本信息,如宿舍号、楼栋、楼层、房间类型(单人间、双人间等)、容纳人数、设施配置等。此外,系统还支持批量导入或导出宿舍信息,方便数据的备份和迁移。 **查询功能**是系统的重要组成部分,它允许管理员和学生根据不同的条件(如宿舍号、楼栋、学生姓名等)快速查找宿舍信息。此外,系统还可以生成各种统计报告,如宿舍占用率、空闲宿舍数量等,以便于决策者进行资源优化。 **卫生检查**功能则是对宿舍卫生状况进行定期评估。管理员可设定检查计划,包括检查周期、评分标准等,并记录每次检查的结果。系统能自动生成卫生报表,用于

    YOLO算法-包装好的服装数据集-654张图像带标签-.zip

    YOLO系列算法目标检测数据集,包含标签,可以直接训练模型和验证测试,数据集已经划分好,包含数据集配置文件data.yaml,适用yolov5,yolov8,yolov9,yolov7,yolov10,yolo11算法; 包含两种标签格:yolo格式(txt文件)和voc格式(xml文件),分别保存在两个文件夹中,文件名末尾是部分类别名称; yolo格式:<class> <x_center> <y_center> <width> <height>, 其中: <class> 是目标的类别索引(从0开始)。 <x_center> 和 <y_center> 是目标框中心点的x和y坐标,这些坐标是相对于图像宽度和高度的比例值,范围在0到1之间。 <width> 和 <height> 是目标框的宽度和高度,也是相对于图像宽度和高度的比例值; 【注】可以下拉页面,在资源详情处查看标签具体内容;

    九缸星形发动机点火器3D

    九缸星形发动机点火器3D

    小程序毕业设计项目-音乐播放器

    本项目可以作为小程序毕设项目,主要功能为音乐播放器,主要功能是:可以播放歌曲(采用mp3网络连接实现)、专辑封面播放时可以旋转,能够实现开始和暂停播放,可以点击下一首歌曲,主页面实现动态轮播图

    出差审批单(表格模板).docx

    出差审批单(表格模板).docx

Global site tag (gtag.js) - Google Analytics