旅途

如果想飞得高,就该把地平线忘掉

异常规范

在异常捕获加入 C++ 几年后,标准化委员会加入了一个叫做异常规范的的补充特性。本文将介绍异常规范并质疑其可用性。

      问题

       考虑下面的函数原型:

       void validate(int code);

       通常,第三方的库把相似的声明分类放在头文件里面,把实现对用户隐藏起来。用户如何知道这个函数是否抛出一个异常和在什么情况下抛出异常呢?显然,这种声明并没有提供任何线索。 Validate() 可能是一个抛出异常的函数,甚至可能是一个完全不知道 C++ 异常的 C 函数抛出了异常。

       异常在 1989 年加入了 C++ 。几年的困扰使得标准化委员会加入了异常规范。

异常规范基础

       一个异常规范描述了一个函数允许抛出哪些异常。没有列在异常规范里的异常将不会从函数里抛出。一个异常规范包括在函数的参数列表后面添加的关键字 throw 。在 throw 的后面是一个异常列表。

void validate(int code) throw (bad_code, no_auth);

       异常规范并不是函数原型的一部分。因此,它不影响重载函数。那就是说,函数指针和成员函数指针可以包括一个异常规范 :

void (*pf)(int) throw(string, int);

       pf 是指向一个可能抛出 string 类型或者 int 类型异常的函数的指针。你可以让其指向一个与其有同样异常规范限制的函数或者一个异常规范限制更严格的函数。如果异常规范 A 的异常集合是异常规范 B 的异常集合的子集,称 A B 的限制更严格。换句话说, A 包含的每个异常都在 B 中,但是反过来却不是这样的。

//more restrictive than pf:

void e(int) throw (string);

//as restrictive as pf:

void f(int) throw (string, int);

//less restrictive than pf:

void g(int) throw (string, int, bool);

pf=e; //fine

pf=f; //fine

pf=g; //error

异常规范和继承

       一个重载虚函数不能扩展在基类的函数里声明的异常集,但是可以缩小它。

       让我们看一个实际的例子。加入你有下面的类层次结构以及相关的异常类集合 :

class clock_fault{/*..*/};

class Exception {/*..*/}; //base for other exceptions

class hardware_fault: public Exception {/*..*/};

class logical_error: public Exception {/*..*/};

class invalid_protocol: public Exception{/*..*/};

 

class RemovableDevice

{

public:

 virtual int

 connect(int port) throw(hardware_fault, logical_error);

 virtual int

 transmit(char * buff) throw(invalid_protocol);

};

 

class Scanner: public RemovableDevice

{

public:

 int connect(int port) throw(hardware_fault); //OK

 int transmit(char * buff)

  throw(invalid_protocol, clock_fault);//Error

};

RemovableDevice::connect() 的异常规范允许它抛出 hardware_fault logical_error 异常 ( 以及任何派生自这些类的异常 ) 。重载函数 Scanner::connect() 缩小了这个规范。从这个函数抛出任何除 hardware_fault 以外的异常,包括 logical_error, 都不允许。

Scanner::transmit() 的异常规范的形式是错误的。它包括一个没有在 RemovableDevice::transmit() 的异常规范里出现的异常 :clock_fault 。如果你尝试编译这段代码,编译器将提示异常规范冲突。

空的异常规范和遗漏异常规范

       一个没有异常规范的函数允许所有的异常。一个异常规范为空的函数不允许任何异常:

class File

{

public:

 int open(FILE *ptr); //may throw any exception

 int close(FILE *ptr) throw(); //doesn't throw

};

当你声明一个空的异常规范,你需要总是检查是否有破坏它的风险。

异常规范的实现

       异常规范在运行时实现。当一个函数破坏了它的异常规范, std::unexpected() 将被调用。 Unexpected() 调用一个之前通过 std::set_unexpected() 注册的用户定义函数。如果没有通过 set_unexpected() 注册函数, unexpected() 调用 std::terminate() 来无条件终止程序运行。

异常规范——理论与实践

       异常规范似乎是备受赞扬的东西。它们不只明确的在文档上提出了一种函数的异常策略,而且 C++ 也实现了它们。

       在开始, C++ 社区强烈的欢迎它们。许多指南和手册作者开始在所有的地方使用它们。课本里的一个典型的类就像这样:

class Foo

{

public:

 Foo() throw();

 ~Foo() throw();

 Foo(const Foo& ) throw();

 Foo& operator=(const Foo &) throw()

//...etc., etc.

};

没过多久程序员们就意识到异常规范相当的麻烦。异常是动态的。不可能总是预见得到在运行时将会抛出哪些异常。如果一个有异常规范的函数 g() 调用一个有更小限制或者没有限制的异常规范的函数 f(), 将会发生什么呢?

void f();

void g() throw(X)

{

 f(); //OK, but problematic

}

如果 f() 抛出一个不是 X 的异常, g() 可能会破坏它的异常规范。

性能是另一个问题。异常总会产生性能代价。执行异常规范还会产生额外的代价,因为实现实在运行时执行的。

因为这些原因以及其他原因,异常规范迅速失去了它们的光彩。今天,你很难再在新的代码或者课本 ( 讽刺的是,第一批采用它们的课本也是第一批悄悄丢弃它们的 ) 里发现它们。

结论

       异常规范是那些理论上似乎可行但是在现实世界被证明是声明狼藉的特性之一。你可能会问我为什么花时间来讨论它们。有两点原因:第一,传统的使用异常规范的代码依然存在。读这样的代码——更重要的是正确的使用它,要求熟悉这一属性。

第二,异常规范在程序语言设计上上了一课。很多程序语言采用 C++ 的异常捕获模型,包括异常规范。当 C++ 社区意识到异常规范没有那么好时,那些语言已经沉迷于这一特性的好处。

今天,有一种要求对 C++ 加入 finally 的压力。这种架构在没有析构函数的 Java 里面很有用。尽管如此,在 C++ 语言里, finally 是多余的,因为在一件异常事件里,你可以通过在析构函数里实现无条件清理操作。因此为什么要提议 finally 呢?很简单,因为 Java 的程序员仍然按照 Java 的思维方式编写 C++ 代码。异常规范告诉我们务必必须对添加一个没有被彻底测试的特性非常小心。

 

翻译自:

http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=109&rl=1

 



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1452376

posted on 2007-09-29 00:57 旅途 阅读(888) 评论(1)  编辑 收藏 引用 所属分类: C/C++

Feedback

# 翻译问题 2010-06-15 14:00 幻の上帝

“异常规范并不是函数原型的一部分。”
↑有误,原文是指“函数的类型”。
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理