在《C++ 类构造函数初始化列表的异常机制 function-try block》这篇文章中,我聊到了这一机制带来的一大好处是提供了一套保证类对像构造过程的安全性的机制。在更前篇《C++ 构造函数初始化列表的好处》中则谈到了这一机制带来的性能上的益处。不过由于我的示例模型建得过于简单,可能让一部分读者对此理解不深,以至于引发了像 yshuise 同学情绪激动的疑惑……这篇我举一两个小例子来更进一步说明问题,解释我的观点。
首先我们要明确一条原则:本地抛出的异常必须本地处理,直接扔给外部的做法是不负责任的。本地处理后,视异常的情况和系统要求,再过滤给外部调用者,即客户,后一动作称为“上抛”。话说我第一次接触这一机制时还是因为 Java……比较汗……
类对像构造函数是很重要的处理过程。在上篇文章谈到了这一机制的好处和重要性。而其必要性则体现在:
1、由于初始化列表中可能涉及到资源分配,因此一旦发生异常,必须完成本地“回滚(RollBack)”动作。“回滚”这个概念,玩儿过数据库的程序员都应该再熟悉不过了。
2、构造函数发生异常时,必须通知调用者构造失败,以保证使用安全。
而这一机制,刚好很漂亮地满足了上述两项关键要求。在构造函数的 catch 块完成后,系统自动上抛异常,交由调用者捕获。
关于本地回滚的重要性,我在此举一个例子。直接上代码:
class Sck { public: Sck () { cout << " Sck Building " << endl; } ~Sck () { cout << " Sck Breaking down " << endl; } }; class Fck { int* sbArray; Sck* sck; public: Fck ( int sbNum ); ~Fck (); }; Fck :: Fck ( int sbNum ) try: sck ( new Sck () ), sbArray ( new int[sbNum] ) { cout << " Fck Constructing " << sbNum << endl; } catch ( bad_alloc& err ) { cout << err.what() << endl; cout << " Fck Failed " << endl; //delete this->sck; }; Fck :: ~Fck () { delete this->sbArray; cout << " Fck Destructing " << endl; } int _tmain(int argc, _TCHAR* argv[]) { Fck fck ( 5 ); //Fck fck_1 ( -1 ); try { Fck fck_1 ( -1 ); } catch ( bad_alloc& err ) { cout << err.what() << endl; cout << " Main Failed " << endl; } catch ( ... ) { } system ( "pause" ); return 0; } |
注意到这段代码比起上篇文章,我添加了 Sck 类,并在 Fck 类中添加了 Sck* sck 成员。初始化列表在生成 sck 对像之后,将在 sbArray 构建数组时发生异常。由于我并没有在构造函数的 catch 块中对 sck 所引资源进行“回滚”操作,则整个程序直到退出后,显示的信息如下:
yshuise 同学还有读者们注意到了,sck 所引资源自始至终都未被释放!由此造成资源悬挂。正确的作法是,将 catch 块中注释掉的 delete sck 恢复。而依 yshuise 同学以为“在构造函数处处理异常是多余的,直接扔给主函数才是正确”的错误说法,也会发生资源悬挂的问题,请读者自行验证。
我之前还曾发过一篇文章,谈到了异常机制中的栈展开过程中的小问题。