构造函数中可不能够抛出异常?固然能够。从语法上来讲,是能够的;从实际状况来看,如今的软件系统日渐庞大和复杂,很难保证 Constructor 在执行过程当中彻底不发生一点异常。html
那么,若是构造函数中抛出异常,会发生什么状况呢?ios
1、构造函数中抛出异常将致使对象的析构函数不被执行。
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{}
的做用域,会自动执行析构函数。运行上述代码,输出结果以下:测试
构造一个对象! 销毁一个对象!
若是在构造函数中抛出一个异常(去掉注释),输出结果以下:url
测试:在构造函数中抛出一个异常
能够看出,析构函数没有被自动执行。为何“构造一个对象!”也没有输出呢?由于程序控制权转移了,因此在异常点之后的语句都不会被执行。spa
2、构造函数抛出异常可能致使内存泄露
#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类的构造函数和析构函数定义以下:.net
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出来的,且在异常抛出以前已经构造彻底,因此它们会自动逆序析构。
3、解决上述内存泄露的方法
由于当对象在构造中抛出异常后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,会致使内存泄露。
-
当对象发生部分构造时,已经构造完毕的子对象(非动态分配)将会逆序地被析构。