踪试一下,无论在发生异常(即调用了 SimulateError)的情况下,还是正常退出(不
调用 SimulateError 或将 SimulateError 的调用改为 Exit)的情况下,AStream.Free()都会得
到执行。
同时拥有 try…except 和 try…finally,应该说是 Delphi 程序员的一种幸运,值得庆幸。
只是,我们想得到的会更多,会希望拥有
try
……
except
……
finally
这样的结构,只是目前还得不到满足。虽然可以用
try
try
……
except
……
end
finally
……
end;
来取代,但显然不如所希望的那样结构美观和优雅。这不能不说是一种遗憾,让我们寄希
望于下一个 Delphi 版本吧!
·58·
异常及错误处理
3.4 构造函数与异常
这个话题在 C++社区中经常会被提起,而在 Delphi 社区中似乎从来没有人注意过,也
许由于语言的特性而使得 Delphi 程序员不必关心这个问题。但我想,Delphi 程序员也应该
3
对该问题有所了解,知道语言为我们提供了什么而使得我们如此轻松,不必理会它。正所
谓“身在福中须知福”。
我们知道,类的构造函数是没有返回值的,因此如果构造函数构造对象失败,则不可
能依靠返回错误代码来解决。那么,在程序中如何标识构造函数的失败呢?最“标准”的
方法就是:抛出一个异常。
构造函数失败,意味着对象的构造失败。那么抛出异常之后,这个“半死不活”的对
象会被如何处理呢?
在此,读者有必要先对 C++对这种情况的处理方式有一个了解。
在 C++中,构造函数抛出异常后,析构函数不会被调用。这种做法是合理的,因为此
时对象并没有被完整构造。
如果构造函数已经做了一些诸如分配内存、打开文件等操作,那么 C++类需要有自己
的成员来记住做过哪些动作。当然,这样做对于类的实现者来说非常麻烦。因此,一般 C++
类的实现者都避免在构造函数中抛出异常(可以提供一个诸如 Init 和 UnInit 的成员函数,
由构造函数或类的客户去调用它们,以处理初始化失败的情况)。而每一本 C++的经典著
作所提供的方案都是使用智能指针(STL 的标准类 auto_ptr)。
在 Object Pascal 中,这个问题变得非常简单,程序员不必为此大费周折。如果 Object
Pascal 的类在构造函数中抛出异常,则编译器会自动调用类的析构函数(由于析构函数不
允许被重载,可以保证只有惟一一个析构函数,因此编译器不会迷惑于多个析构函数之中)。
析构函数中一般会析构成员对象,而 Free()方法保证了不会对 nil 对象(即尚未被创建的成
员对象)调用析构函数,因此在使得代码简洁优美的前提下,又保证了安全。
以下的程序演示了构造函数中抛出异常后,Object Pascal 编译器所作的处理方法。
首先定义 TMyClass:
type
TMyClass = class
private
FStr : PChar; // 字符串指针
public
constructor Create();
destructor Destroy(); override;
end;
然后实现 TMyClass,并让它的构造函数中抛出异常:
·59·
Delphi 高手突破
constructor TMyClass.Create();
begin
FStr := StrAlloc(10); // 构造函数中为字符串指针分配内存
StrCopy(FStr, 'ABCDEFGHI');
raise Exception.Create('error'); // 抛出异常,没有理由
end;
destructor TMyClass.Destroy();
begin
StrDispose(FStr); // 析构函数中释放内存
WriteLn('Free Resource');
end;
最后,编写程序主流程的代码。主流程中首先创建 TMyClass 类的实例:
var
Obj : TMyClass;
i : integer;
begin
try
Obj := TMyClass.Create();
// Obj.Free(); // 不调用析构函数,但发生异常时,编译器自动调用了析构函数
WriteLn('Succeeded');
except
Obj := nil;
WriteLn('Failed');
end;
Read(i); // 暂停屏幕,以便观察运行结果
end.
这段代码中,创建 TMyClass 类的实例时遇到了麻烦,因为 TMyClass 的构造函数抛出
了异常,但这段代码执行结果却是:
Free Resource
Failed
出现了“Free Resource”,说明发生异常后,析构函数被调用了。而这正是在构造函
数抛出异常之后,编译器自动调用析构函数的结果。
因此,如果类的说明文档或类的作者告知你,类的构造函数可能会抛出异常,那就要
记得用 try…except 包住它!
·60·
异常及错误处理
C++与 Object Pascal 对于构造函数抛出异常后的不同处理方式,其实正是两种语言的
设计思想的体现。C++秉承 C 语言的风格,注重效率,一切交给程序员来掌握,编译器不
做多余动作;Object Pascal 继承 Pascal 的风格,注重程序的美学意义,编译器帮助程序员
完成复杂的工作。
3.5 小 结 3
异常是面向对象编程带来的非常好的工具,不加以利用是很可惜的。但是,正如万事
都有个“度”,滥用异常也是不可取的。使用异常不是没有代价,它会增加程序的负担,
编写若干 try...except 和编写数以千计的 try...except 之间是有很大区别的。
同时,也不必过分害怕由它所带来的负担。其实,既然已经使用了 Delphi,其实就已
经在使用异常了,也许只是自己还不知道。听听 Chalie Calverts 的忠告:“在似乎有用的
时候,就应该使用 try...except 块。但是要试着让自己对这种技术的热情不要太过分”。