C++学习之构造函数中的异常处理
构造函数中可不可以抛出异常?当然可以。从语法上来说,是可以的;从实际情况来看,现在的软件系统日渐庞大和复杂,很难保证 Constructor 在执行过程中完全不发生一点异常。
那么,如果构造函数中抛出异常,会发生什么情况呢?
一、构造函数中抛出异常将导致对象的析构函数不被执行。
C++仅能 delete 被完全构造的对象(fully constructed objects),只有一个对象的构造函数完全运行完毕,这个对象才被完全地构造。所以如果在构造函数中抛出一个异常,这个异常将传递到创建对象的地方(程序控制权也会随之转移),这样对象就只是部分被构造,它的析构函数将不会被执行。
看下面的示例:
#pragma once
#include <iostream>
#include <string>
using namespace std; /******************类定义**********************/
class person {
public:
person(const string& str):name(str)
{
//throw exception("测试:在构造函数中抛出一个异常");
cout << "构造一个对象!" << endl;
};
~person()
{
cout << "销毁一个对象!" << endl;
};
private:
string name;
}; /******************测试类**********************/
int main()
{
try
{
person me("songlee");
}
catch(exception e)
{
cout << e.what() << endl;
};
getchar();
return 0;
}
注意上面的 me 是一个局部对象,所以离开try{}的作用域,会自动执行析构函数。运行上述代码,输出结果如下:
构造一个对象!
销毁一个对象!
如果在构造函数中抛出一个异常(去掉注释),输出结果如下:
测试:在构造函数中抛出一个异常
可以看出,析构函数没有被自动执行。为什么“构造一个对象!”也没有输出呢?因为程序控制权转移了,所以在异常点以后的语句都不会被执行。
二、构造函数抛出异常可能导致内存泄露
#pragma once
#include <string>
#include <iostream>
using namespace std; class A {
public:
A(){};
}; class B {
public:
B() {
//throw exception("测试:在B的构造函数中抛出一个异常");
cout << "构造 B 对象!" << endl;
};
~B(){ cout << "销毁 B 对象!" << endl; };
}; class Tester {
public:
Tester(const string& name, const string& address);
~Tester();
private:
string theName;
string theAddress;
A *a;
B *b;
};
上面声明了三个类(A、B、Tester),其中Tester类的构造函数和析构函数定义如下:
Tester::Tester(const string& name, const string& address):
theName(name),
theAddress(address)
{
a = new A();
b = new B(); // <——
cout << "构造 Tester 对象!" << endl;
} Tester::~Tester()
{
delete a;
delete b;
cout << "销毁 Tester 对象!" << endl;
}
在构造函数中,动态的分配了内存空间给a、b两个指针。析构函数负责删除这些指针,确保Tester对象不会发生内存泄露(C++中delete一个空指针也是安全的)。
int main()
{
Tester *tes = NULL;
try
{
tes = new Tester("songlee","201");
}
catch(exception e)
{
cout << e.what() << endl;
};
delete tes; // 删除NULL指针是安全的
getchar();
return 0;
}
运行输出结果:
构造 B 对象!
构造 Tester 对象!
销毁 B 对象!
销毁 Tester 对象!
看上去好像一切良好,在正常情况下确实没有错。但是在有异常的情况下,恐怕就不会良好了。
试想在 Tester 的构造函数执行时,b = new B()抛出了异常:可能是因为operator
new不能给B对象分配足够的内存,也可能是因为 B 的构造函数自己抛出了一个异常。不论什么原因,在 Tester 构造函数内抛出异常,这个异常将传递到建立 Tester 对象的地方(程序控制权也会转移)。
在 B 的构造函数里抛出异常(去掉注释)时,程序运行结果如下:
测试:在B的构造函数中抛出一个异常
可以看出,C++拒绝为没有完成构造操作的对象调用析构函数,即使你使用了delete语句。由于 Tester 的析构函数不会执行,所以给A对象 a
动态分配(new)的空间无法释放,将造成内存泄露。
注:不用为 Tester 对象中的非指针数据成员操心,因为它们不是new出来的,且在异常抛出之前已经构造完全,所以它们会自动逆序析构。
三、解决上述内存泄露的方法
因为当对象在构造中抛出异常后C++不负责清除(动态分配)的对象,所以你必须重新设计构造函数以让它们自己清除。常用的方法是捕获所有的异常,然后执行一些清除代码,最后再重新抛出异常让它继续传递。
示例代码如下:
Tester::Tester(const string& name, const string& address):
theName(name),
theAddress(address),
a(NULL), // 初始化为空指针是必须的
b(NULL)
{
try
{
a = new A();
b = new B();
}
catch(...) // 捕获所有异常
{
delete a;
delete b;
throw; // 继续传递异常
}
}
另一种更好的方法是使用智能指针(smart pointer),不过关于智能指针的内容比较多,在这里就不说了。
总结:
在构造函数中抛出异常是C++中通知对象构造失败的唯一方法。
构造函数中抛出异常,对象的析构函数将不会被执行。
构造函数抛出异常时,本应该在析构函数中被delete的对象没有被delete,会导致内存泄露。
当对象发生部分构造时,已经构造完毕的子对象(非动态分配)将会逆序地被析构。
个人站点:http://songlee24.github.com
C++学习之构造函数中的异常处理的更多相关文章
- JavaScript学习13 JavaScript中的继承
JavaScript学习13 JavaScript中的继承 继承第一种方式:对象冒充 <script type="text/javascript"> //继承第一种方式 ...
- JavaScript学习12 JS中定义对象的几种方式
JavaScript学习12 JS中定义对象的几种方式 JavaScript中没有类的概念,只有对象. 在JavaScript中定义对象可以采用以下几种方式: 1.基于已有对象扩充其属性和方法 2.工 ...
- 第65课 C++中的异常处理(下)
1. C++中的异常处理 (1)catch语句块可以抛出异常 ①catch中获捕的异常可以被重新抛出 ②抛出的异常需要外层的try-catch块来捕获 ③catch(…)块中抛异常的方法是throw; ...
- C++中的异常处理(二)
C++中的异常处理(二) 标签: c++C++异常处理 2012-11-24 20:56 1713人阅读 评论(2) 收藏 举报 分类: C++编程语言(24) 版权声明:本文为博主原创文章,未经 ...
- 为什么super()和this()调用语句不能同时在一个构造函数中出现的解释
我想这应该是Java构造函数的一种机制吧,首先以子类和父类为例.当你创建一个子类的实例时,首先会调用父类的构造函数,然后再调用子类的构造函数,如果父类中没有缺省构造函数,则必须再子类的构造函数中显示的 ...
- [改善Java代码]不要在构造函数中抛出异常
Java的异常机制有三种: 一.Error类以及其子类表示的是错误,它是不需要程序员处理也不能处理的异常.比如VirtualMachineError虚拟机错误,ThreadDeath线程僵尸等. 二. ...
- JavaScript学习12 JS中定义对象的几种方式【转】
avaScript学习12 JS中定义对象的几种方式 转自: http://www.cnblogs.com/mengdd/p/3697255.html JavaScript中没有类的概念,只有对象. ...
- 学习C++ -> 构造函数与析构函数
学习C++ -> 构造函数与析构函数 一.构造函数的介绍 1. 构造函数的作用 构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建对象时, 对象会自动调用 ...
- [翻译] ASP.NET WebAPI 中的异常处理
原文链接:https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling 本文介绍 ...
随机推荐
- xamarin.forms 绑定页面里指定元素的某个属性值
{Binding Source={x:Reference elementName}, Path=BindingContext.propertyname, Mode=OneWay} elementNam ...
- CAD参数绘制直线(com接口)
用户可以在CAD控件视区任意位置绘制直线. 主要用到函数说明: _DMxDrawX::DrawLine 绘制一个直线.详细说明如下: 参数 说明 DOUBLE dX1 直线的开始点x坐标 DOUBLE ...
- count() 方法
count() :方法用于统计字符串里某个字符出现的次数.可选参数为在字符串搜索的开始与结束位置. num1,num2 = input('请输入字符串:'),input('请输入要查询的子串:') p ...
- IDEA -- idea无法导入HttpServlet包解决方法
IntelliJ IDEA 没有导入 servlet-api.jar 这个架包,需要你手动导入支持. 步骤1: 步骤2: 步骤3: 在弹出框中找到Tomcat安装路径 下的lib文件夹..中的Serv ...
- mysql通配符进行模糊查询
在mysql数据库中,当我们需要模糊查询的时候 ,我们会使用到通配符. 首先我们来了解一下2个概念,一个是操作符,一个是通配符. 操作符 like就是SQL语句中的操作符,它的作用是指示在SQL语句后 ...
- jquery data属性 attr vs data
html5的自定义data属性相信大家都不会陌生,有了它你可以绑定所需的数据到指定元素上.然后通过jquery设置.获取数据,简直开心的不行啊.想到设置.获取元素属性值,大家一定首先想到了jquery ...
- mysql负载均衡
一.docker安装haproxy:docker pull haproxy 二.配置haproxy(参考url:https://zhangge.net/5125.html),vim /usr/loca ...
- HDU 1016 素数环问题
题目大意: 给定1-n这n个数,组成以1开头的素数环,保证相邻两个数相加均为素数 题目用dfs搜索再回溯,这样碰到不成立的立刻退出递归,就减少了很多步骤,不然暴力来就是n!次复杂度,肯定是超时的 每次 ...
- [luoguP1944] 最长括号匹配_NOI导刊2009提高(1)
传送门 非常傻的DP. f[i]表示末尾是i的最长的字串 #include <cstdio> #include <cstring> #define N 1000001 int ...
- 刺激(codevs 1958)
题目描述 Description saffah的一个朋友S酷爱滑雪,并且追求刺激(exitement,由于刺激过度导致拼写都缺了个字母),喜欢忽高忽低的感觉.现在S拿到了一张地图,试图制定一个最长路径 ...