C++ 异常 exception (1)
// 1:在构造函数中抛出异常,则系统不会执行该对像的析构函数!!!
// 2:在构造函数中抛出异常,如果对象是在堆中创建的,那会,会delete掉这块内存.但此时的 delete已经不会再调用析构函数了
// 3:抛出异常后,异常语句后面的代码不再执行,但是编译器会先把类内部成员(已经构造好的对钟爱)的析构函数调用完后再跳出try()
// 4:每个异常,必须有相应的try将其包围,包括 显式的手工制作的异常.eg throw int(),
也包括 未知的代码 执行过程中出现的异常,比如MakeXML中出现的异常.eg.string中可能出现的异常
同样的,每个异常必须有一个catch()将其捕捉
// :每个catch 必须至少有一个类型.也可是catch(...)这样的万能类型
// :如果catch 没有捕捉到 try所抛出的对应的异常,出现unhandle exception.:当一个异常出现却没有被处理.那么发布后的release程序会.运行后,什么结果也不显示,直接关闭,什么也不提示,!!!!,这种情况发现时,让你去找bug将会是多么恐怖!!!
发布为debug,则会显示一个 未处理的异常之类的类似于断言失败的对话框.
1. 指导方针
根据读者们的建议,经过反思,我部分修正在Part14中申明的原则:
l 只要可能,使用那些构造函数不抛异常的基类和成员子对象。
l 不要从你的构造函数中抛出任何异常。
这次,我将思考读者的意见,C++先知们的智慧,以及我自己的新的认识和提高。然后将它们转化为指导方针来阐明和引申那些最初的原则。
(关键字说明:我用“子对象”或“被包容对象”来表示数组中元素、无名的基类、有名的数据成员;用“包容对象”来表示数组、派生类对象或有数据成员的对象。)
1.1 C++的精髓
你可能认为构造函数在遇到错误时有职责抛异常以正确地阻止包容对象的构造行为。Herb Sutter在一份私人信件中写道:
一个对象的生命期始于构造完成。
推论:一个对象当它的构造没有完成时,它从来就没存在过。
推论:通报构造失败的唯一方法是用异常来退出构造函数。
我估计你正在做这种概念上就错误的事(“错”是因为它不符合C++的精髓),而这也正是做起来困难的原因。
“C++的精髓”是主要靠口头传授的C++神话。它是我们最初的法则,从ISO标准和实际中得出的公理。如果没有存在过这样的C++精髓的圣经,混乱将统治世界。Given that no actual canon for the Spirit exists, confusion reigns over what is and is not within the Spirit, even among presumed experts.
C和C++的精髓之一是“trust the programmer”。如同我写给Herb的:
最终,我的“完美”观点是:在错误的传播过程中将异常映射为其它形式应该是系统设计人员选定的。这么做不总是最佳的,但应该这么做。C++最强同时也是最弱的地方是你可以偏离你实际上需要的首选方法。还有一些其它被语言许可的危险影行为,取决于你是否知道你正在做什么。In the end, my "perfect" objective was to map exceptions to some other form of error propagation should a designer choose to do so. Not that it was always best to do so, but that it could be done. One of the simultaneous strengths/weaknesses of C++ is that you can deviate from the preferred path if you really need to. There are other dangerous behaviors the language tolerates, under the assumption you know what you are doing.
C++标准经常容忍甚至许可潜在的不安全行为,但不是在这个问题上。显然,认同程序员的判断力应该服从于一个更高层次的目的(Apparently, the desire to allow programmer discretion yields to a higher purpose)。Herb在C++精髓的第二个表现形式上发现了这个更高层次的目的:一个对象不是一个真正的对象(因此也是不可用的),除非它被完全构造(意味着它的所有要素也都被完全构造了)。
看一下这个例子:
struct X
{
A a;
B b;
C c;
void f();
};
try
{
X x;
x.f();
}
catch (...)
{
}
这里,A、B和C是其它的类。假设x.a和x.b的构造完成了,而x.c的构造过程中抛了异常。如我们在前面几部分中看到的,语言规则规定执行这样的序列:
l x的构造函数抛了异常
l x.b的析构函数被调用
l x.a的析构函数被调用
l 控制权交给异常处理函数
这个规则符合C++的精髓。因为x.c没有完成构造,它从未成为一个对象。于是,x也从未成为一个对象,因为它的一个内部成员(x.c)从没存在过。因为没有一个对象真的存在过,所以也没有哪个需要正式地析构。
现在假设x的构造函数不知怎么控制住了最初的异常。在这种情况下,执行序列将是:
l x.f()被调用
l x.c的析构函数被调用
l x.b的析构函数被调用
l x.a的析构函数被调用
l x的析构函数被调用
l 控制权跳过异常处理函数向下走
于是异常将会允许析构那些从没被完全构造的对象(x.c和x)。这将造成自相矛盾:一个死亡的对象是从来都没有产生过的。通过易做图构造函数抛异常,语言构造避免了这种矛盾。
1.2 C++的幽灵
前面表明一个对象当且仅当它的成员被完全构造时才真的存在。但真的一个对象存在等价于被完全构造?尤其x.c的构造失败“总是”如此恶劣到x必须在真的在被产生前就死亡?
在C++语言有异常前,x的定义过程必定成功,并且x.f()的调用将被执行。代替抛异常的方法,我们将调用一个状态检测函数:
X x;
if (x.is_OK())
x.f();
或使用一个回传状态参数:
bool is_OK;
X x(is_OK);
if (is_OK)
x.f();
在那个时候,我们不知何故在如x.c这样的子对象的构造失败时没有强调:这样的对象从没真的存在过。那时的设计真的这么根本错误(而我们现在绝不允许的这样行为了)? C++的精髓真的在那时是不同的?或者我们生活在梦中,没有想到过x真的没有成形、没有存在过?
公正地说,这个问题有点过份,因为C++语言现在和过去相比已不是同样的语言。将老的(异常支持以前)的C++当作现在的C++如同将C当作C++。虽然它们有相同的语法,但语意却是不相同的。看一下:
struct X
{
X()
{
p = new T; // assume new fails
}
void f();
};
X x;
x.f();
假设new语句没有成功分配一个T对象。异常支持之前的编译器(或禁止异常的现代编译器)下,new返回NULL,x的构造函数和x.f()被调用。但在异常允许后,new抛异常,x构造失败,x.f()没有被调用。同样的代码,非常不同的含意。
在过去,对象没有自毁的能力,它们必须构造,并且依赖我们来发现它的状态。它们不处理构造失败的子对象。并且,它们不调用标准运行库中抛异常的库函数。简而言之,过去的程序和现在的程序存在于不同的世界中。我们不能期望它们对同样的错误总有同样的反应。
1.3 这是你的最终答案吗?
我现在相信C++标准的行为是正确的:构造函数抛异常将析构正在处理的对象及其包容对象。我不知道C++标准委员会制订这个行为的精确原因,但我猜想是:
l 部分构造的对象将导致一些微妙的错误,因为它的使用者对其的构造程度的假设超过了实际。同样的类的不同对象将会有出乎意料的和不可预测的不同行为。
l 编译器需要额外的纪录。当一个部分构造的对象消失时,编译器要避免对它及它的部分构造的子对象调用析构函数。
l 对象被构造和对象存在的等价关系将被打破,破坏了C++的精髓。
1.4 对对象的使用者的指导
异常是对象的接口的一部分。如果能够,事先准备好接口可能抛的异常集。如果一个接口没有提供异常规格申明,而且又不能从其它地方得知
补充:软件开发 , C++ ,