为什么C++编译器不会检查异常?

45

C++提供了用于检查异常的语法,例如:

void G() throw(Exception);
void f() throw();
然而,Visual C++编译器并未检查它们;抛出标志被简单地忽略了。在我看来,这使得异常功能无法使用。因此我的问题是:有没有一种方法可以让编译器检查异常是否被正确捕获/重新抛出?例如使用Visual C++插件或不同的C++编译器。

顺便说一下,我希望编译器能够检查异常是否被正确捕获,否则你最终会陷入一个境地,即必须在每个函数调用周围都放置一个catch语句,即使它们明确声明不会抛出任何异常

更新:当在一个带throw()标记的函数中抛出异常时,Visual C++编译器会显示警告。这很好,但遗憾的是,当调用可能会抛出异常的子例程时,警告不会出现。例如:

void f() throw(int) { throw int(13); }
void h() throw() { g(); } //no warning here!

我从未听说过C++中的checked exceptions,但我知道异常规范,并且Visual C++对它们并不是很在意,可以看这里:http://msdn.microsoft.com/en-us/library/wfa0edys.aspx - Skurmedel
1
Visual C++并不完全忽略它...在函数末尾添加throw()告诉Visual C++,为了优化等目的,可以假定该函数不会抛出异常,如果该函数实际上确实抛出异常,则可能发生任何事情。标准规定,如果一个被标记为throw()的函数因为异常而退出,则会调用std::unexpected(),通常会抛出std::bad_exception。这与“可能发生任何事情”非常不同。 - Doug
如果异常规范只是编译器的提示而不是要求做某些事情,它们可能会有用。在我看来,标准行为是无用的。 - David Thornley
6个回答

44

有趣的是,Java有检查异常,而Java程序员也讨厌它们。

C++中的异常规范有三个无用之处:

1. C++异常规范会抑制优化。

除了throw()可能的情况外,编译器会插入额外的代码来检查在堆栈展开期间,当您引发异常时,它是否与函数的异常规范匹配。这样会使您的程序变慢。

2. C++异常规范不是由编译器强制执行的

就编译器而言,以下语法是正确的:

void AStupidFunction() throw()
{
    throw 42;
}

更糟糕的是,如果你违反了异常规范,将不会发生任何有用的事情。你的程序将会直接终止!

3. C++异常规范是函数签名的一部分。

如果你有一个带有虚函数的基类并试图重写它,异常规范必须完全匹配。因此,最好提前计划,但这仍然很麻烦。

struct A
{
    virtual int value() const throw() {return 10;}
}

struct B : public A
{
    virtual int value() const {return functionThatCanThrow();} // ERROR!
}

异常规格会带来这些问题,而使用它们的收益很小。相比之下,如果完全避免使用异常规格,则编码更容易,而且可以避免这些问题。


23
作为一名Java程序员,我实际上不讨厌受检异常,相反我很喜欢它,因为在很多情况下可以避免许多潜在的问题。 - Searene
使用未经检查的异常违反了具有静态类型语言的原则。 - smls
1
就开放/封闭原则而言,如果检查异常违反了它,那么你同样可以认为函数返回类型也违反了它。 - smls
@smls 检查异常违反了开闭原则,因为在层次结构底部的函数中添加一个异常将要求您修改调用该低级函数的每个对象并更改它们的接口。修改一个返回值不会这样做:您可以在调用它的对象中处理新的返回值,而无需修改它们的接口。您应该阅读我在评论中引用的章节。 - jciloa
1
已检查异常不违反开闭原则,因为您可以在调用低级函数的对象中处理新异常,而无需修改它们的接口。 - Izaak Weiss
显示剩余2条评论

35

在C++中,异常规格说明相当无用。

它并不能强制要求不会抛出其他异常,只是保证全局函数unexpected()将被调用(可以设置它),

使用异常规格说明主要归结为欺骗自己(或同事)产生虚假的安全感。最好干脆不去理会。


2
“不可能”是一个相当强烈的说法。为什么在模板中实施异常规范是不可能的呢? - Rob Kennedy
1
想了想,你当然是对的,我不知道我怎么会有那个想法,已经删除该段落。 - Pieter
2
正确的情感,但完全错误的论点。C++(不像Java和大多数其他现代语言)在运行时而不是编译时检查异常规范。不幸的是,这种异常规范的实验并没有成功(除了无抛出)。 - Martin York

15

看看这个:

http://www.gotw.ca/publications/mill22.htm

基本上,异常规范是不可行/无法使用的,但这并不能使异常不可行。

至于你的问题,没有办法让编译器检查每种抛出的类型是否在代码的更高层次上某处被捕获,我预计编译单元会使此过程变得困难,而且对于在库中使用的代码来说这是不可能的(因为在编译时顶层代码不可用)。如果你想确保所有异常都被捕获,那么就将 catch(...) 放置在你代码的最顶部。


1
感谢您的回答。在顶层使用catch(...)的问题是,在C++中,您几乎无法获得有关发生情况的任何信息:没有类型信息和没有调用堆栈。通过以一种非常严格的方式抛出单一类型的异常可以解决此问题,但这感觉很容易出错。 - Dimitri C.
1
@ Dimitri:我不同意,总是抛出派生自std::exception的异常,没有必要有很大的纪律性来做到这一点... - Patrick
没有办法让编译器检查每个抛出的类型是否在代码的更高层被捕获,但他们可以让链接器做到这一点,对库进行声明或处理,并在可执行文件中进行处理。 - cp.engr

9

在运行之前检测如以下情况的解决方案:

extern void f() throw (class Mystery);
void g() throw() { 
    f() ; 
}

如果你需要进行静态分析,那么编译器将会执行大量的静态分析。但是因为标准是“如果抛出的内容与指定不匹配则引发std :: unexpected”,而且可以完全合法地编写一个抛出不匹配指定的对象的程序,所以编译器实现者既不会警告也不会备注。

声称提供警告服务的静态分析工具包括Gimpel Software的C ++ lint...

1560未捕获的异常'Name'未在函数'Symbol'的throw-list中

根据之前问题的这个答案,还有QA C ++


9

因为标准规定如此。异常声明并不意味着没有其他异常会被抛出。它意味着如果抛出未声明的异常,将调用一个特殊的全局函数 unexpected(),默认情况下终止程序。通常不鼓励在函数中声明异常(也许除了空异常列表),因为标准行为并不是很有帮助。


0

我无法检查缺少MSVC安装,但您确定编译器会忽略throw()规范吗?

这个MSDN页面表明Microsoft已经意识到throw()并期望他们的编译器正确处理它。好吧,几乎是的,在某些细节上他们不遵循ANSI/ISO标准,请参见有关说明。

编辑:实际上,我同意Patrick的观点:异常规范大多数情况下是无用的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接