C_and_CPP 版 (精华区)

发信人: seaboy (浪小), 信区: C_and_CPP
标  题: Baseless Exceptions 
发信站: 哈工大紫丁香 (2003年06月23日08:39:19 星期一), 站内信件

Baseless Exceptions 
WQ翻译

-------------------------------------------------------------------------------
-

“嗯,不错,不错,”我自我感觉良好。“看起来我发现了一个编译器错误。” 

我正在追踪一个故障,有一个异常没有被异常处理体系捕获。精简后的代码如下: 

class Base 



// ...

whatever ... 

};  

class Derived : private Base 



void f() { 

    try { 

        throw Derived(); 

    } 

    catch (Base &) { 

        cout << "Caught Base" << endl; 

    }

    catch (...)

    { 

        cout << "Caught something else" << endl; 

    } 



}; 

我预期程序会隐式地将Derived对象转换为Base &,因此会打印出 "捕获Base"。然而,实
际上,它没有转换而打印出 "Caught something else"。沾沾自喜于发现了编译器的错误
时,我记起了Guru的教导:“记住,徒弟,绝不要假设你或编译器中的任何一个是正确的
。总是查神圣的标准来确定你的理解和智慧。”几分钟之后,我的自喜已转变为迷惑,因
为标准说编译器的行为是正确的: 

一个捕获程序匹配于抛出的类型E的对象,条件是……捕获程序的参数类型是cv T或cv T&
并且T是E的一个无二义公有基类……[1] 

无二义的公有基类?好吧,这有些不合理,我认为。毕竟,那个Derived被转换为Base对象
,因为它处在自己的代码空间中。为了测试这个问题,我在try语句前增加了一句语句 

Base & testRef = *this;

肯定,它没有任何编译问题。 

“你看起来很迷惑,徒弟。”我轻轻抖了一下-我不认为我会习惯于Guru的突然出现。 

“嗯,是的,”我嘟囔着。“我不理解标准为什么不允许异常被类型转换,而在代码中又
是允许的。” 

她在捧着的“砖头”上小心地作记号了然后合上。“在语句'Base & testRef = *this;'上
发生的类型转换,编译器什么时候检查转换是否允许进行,以及取决于存取权限还是继承
?” 

“嗯,当它构造这个引用的时候,它进行检查。不,等等-说错了。它在编译的时候。” 

“正确,孩子。接下来,编译器什么时候为异常捕获程序要求的类型转换作检查?” 

“它也发生于编译期,不是吗?”Guru没回答。实际上,她是扬起眉毛,用灰色的眼睛盯
着我-这是她的“你确定吗?”。我死命想了一会儿。然后明白了。“哦,当然,”我说
。“评估发生在运行期。任何类型的对象都可以被抛出,所以异常捕获程序必须查看实际
对象的类型。” 

“而访问权限信息在运行期存放在哪里?”Guru提示道。 

“它被储存于……”我停了一下。“嗨,这有个陷阱-访问检查应该在编译期进行,不会
存储任何运行期信息。啊,我现在明白了-这里存在限制是因为缺乏访问权限信息。” 

我以为Guru会于此时离开。然而,她没有。她只是静静地站在我的隔间旁边。我又想了一
下。 

“等一下,”我慢慢地说。“不,我还是没明白。程序仍然必须知道,是否存在一个无二
义的公有基类以进行转换。这会发生什么……”我转向白板,写了个比较复杂的例子: 

#include 

using namespace std; 

class Base 



public: 

virtual ~Base(){} 

}; 

class DerivedPrivately : private Base 



// ...

whatever ... 

}; 

class DerivedPublicly : public Base 



// ...

whatever ... 

}; 

void g(int which) 



if (which < 0) throw DerivedPublicly(); 

else if (which == 0) throw Base(); 

else throw DerivedPrivately(); 



void f(int value) 



try { 

        g(value); 



catch(Base &) 



        cout << "Caught Base" << endl; 



catch ( ... ) 



        cout << "Caught something else" << endl; 





int main() 



f(-1); 

f(0); 

f(1); 



“如果我正确地理解了标准,前两个调用的输出应该是'Caught Base',最后一个是'Caugh
t something else'。编译器显然做了某种运行期检查,所以限定它为公有无二义基类?”
 

“啊,孩子,如果你修改私有派生出的类,加一句friend void f(int);语句。然后你预料
会发生什么?” 

“ 嗯, DerivedPrivately对象已经申明f()为友元,所以我预料它打印出'Caught Base'
。” 

她微笑了一下。“现在,程序需要什么信息以知道类型转换是可接受的?” 

我曾经提到Guru的Socratic方法是多么令人苦恼吗?我思索了一下。“嗯,”我冒险一试
,“显然,它必须跟踪继承树。而且,我猜测它也必须跟踪友元。这个应该实现起来很容
易,虽然-只要在f()的obj文件中生成一些有序表。” 

“啊,但如果f()不知道它是DerivedPrivately友元时怎么办?也就是说,f()所在的编译
单元只引用了Base,它根本没有包含申明DerivedPrivately的头文件。不,我的孩子,信
息必须全局存储,而且程序需要某种方式将特定的机器码映射到特定的函数,以判决某函
数是否作为友元在执行。在复杂体系层次中,可能有任意的友元或隐含类型转换顺序,维
护这些信息会是一项重大开销,而还可能并不必要。维护一个无二义的公有基类列表就简
单多了。” 

“顺便一提,你会注意到,同样的限制也适用于捕获指针的情况。编译器只将会一个指针
转换指向它所指类型的无二义公有基类类型的指针。指针确实有额外的能力执行受限的转
换。” 

Guru转身离开了。“解决办法是明摆着的了,徒弟。顺便提一句,神圣的标准要求进行运
行期访问权限的检查的地方仅有两处,异常捕获是其中之一。另外一处当然是……”但是
她转了弯,于是我就没听清余下的部分。 

当然,Guru是正确的。解决办法很简单:为Derived增加一个异常捕获: 

class Derived : private Base 



void f() { 

        try { 

        throw Derived(); 

        } 

    catch (Derived &) { 

            cout << "Caught Derived" << endl; 

    }

        catch (Base &) { 

cout << "Caught Base" << endl; 

    }

        catch (...) { 

cout << "Caught something else" << endl; 

    } 



}; 


-------------------------------------------------------------------------------
-

[注释] 
[1] ISO C++ Standard, clause 15.3 paragraph 3. 

--
欢迎到C_and_CPP版讨论相关问题。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.80]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:8.411毫秒