在函数签名中抛出关键字


199

throw在函数签名中使用C ++ 关键字被视为不良做法的技术原因是什么?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

看到最近的相关问题:stackoverflow.com/questions/1037575/…–
laalto


1
noexcept改变吗?
亚伦·麦克戴德

12
对与编程相关的东西有意见并没有错。至少对于这个问题,这个结案标准是不好的。这是一个有趣的问题,答案很有趣。
安德斯·林登

应当注意,自C ++ 11起不赞成使用异常规范
弗朗索瓦·安德里厄

Answers:


127

不,这不是一种好的做法。相反,通常认为这是一个坏主意。

http://www.gotw.ca/publications/mill22.htm详细介绍了原因,但是部分原因是编译器无法强制执行此操作,因此必须在运行时进行检查,通常不受欢迎的。而且在任何情况下都无法很好地支持它。(MSVC忽略了异常规范,throw()除外,该规范被解释为保证不会引发异常。


25
是。有比向throw(myEx)添加空格更好的方法。
阿萨夫·拉维(

4
是的,刚发现异常规范的人通常会认为它们的工作方式类似于Java,在Java中编译器可以执行这些规范。在C ++中,这种情况不会发生,这使它们的使用效率大大降低。
jalf

7
但是记录的目的呢?此外,系统会告诉您即使尝试也不会捕获哪些异常。
安德斯·林登

1
@AndersLindén的文档用途是什么?如果只想记录代码的行为,只需在其上方添加注释即可。不确定第二部分的含义。即使尝试,您也永远不会捕捉到异常情况?
jalf

4
我认为代码在记录代码方面非常强大。(评论可能会说谎)。我指的是“记录性”效果,您可以肯定地知道可以捕获哪些异常,而其他则不能。
安德斯·林登

57

Jalf已经链接到它了,但是GOTW很好地说明了为什么异常规范没有人们希望的有用:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

评论正确吗?不完全的。Gunc()可能确实会丢东西,并且Hunc()可能会丢掉A或B以外的东西!编译器只是保证在它们发生错误时击败他们……哦,并且在大多数情况下也使您的程序失去意义。

这就是它的最终结果,您可能最终会调用,terminate()并且您的程序死于快速但痛苦的死亡。

GOTW的结论是:

因此,这似乎是我们作为一个社区迄今为止所获得的最佳建议:

  • 道德准则1:永远不要编写异常规范。
  • 道德2:除了可能是一个空洞的人以外,但如果我是你,我什至会避免。

1
我不确定为什么会引发异常并且无法提及。即使它被另一个函数抛出,我也知道会抛出什么异常。我能看到的唯一原因是因为它很乏味。
2013年

3
@Ken:关键是编写异常规范会带来负面影响。唯一的积极效果是,它向程序员显示了可能发生的异常,但是由于编译器没有以合理的方式对其进行检查,因此容易出错,因此不值得花很多钱。
2013年

1
哦,好的,谢谢您的回复。我想这就是文档的目的。
2013年

2
不正确。应该编写异常规范,但是其思想是传达调用者应尝试捕获的错误。
HelloWorld '09

1
就像@StudentT所说的:函数的责任是保证不引发其他任何异常。如果它们这样做,程序将按预期终止。声明抛出异常意味着我没有责任处理这种情况,并且呼叫者应该有足够的信息来执行此操作。不声明异常意味着它们可以在任何地方发生,并且可以在任何地方进行处理。那肯定是反OOP混乱。未能在错误的地方捕获异常是设计失败。我建议不要抛出空,因为异常是例外,大多数函数无论如何都应该抛出空。
JanTuroň17年

30

要为该问题的所有其他答案增加更多的价值,您应该在该问题上花费几分钟:以下代码的输出是什么?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

答:如前所述这里,程序调用std::terminate(),因此没有任何异常处理程序将被调用。

详细信息:my_unexpected()调用了第一个 函数,但是由于它没有为throw_exception()函数原型重新抛出匹配的异常类型,因此最后std::terminate()调用了。因此完整的输出如下所示:

user @ user:〜/ tmp $ g ++ -oexcept.testexcept.test.cpp
user @ user:〜/ tmp $ ./except.test
很好-这是
在抛出'int'
中止实例后终止的意外终止倾销)


12

抛出说明符的唯一实际效果是,如果myExc您的函数抛出了与异常不同的东西,std::unexpected则会被调用(而不是正常的未处理异常机制)。

为了记录函数可以引发的异常类型,我通常这样做:

bool
some_func() /* throw (myExc) */ {
}

5
注意到对std :: unexpected()的调用通常会导致对std :: terminate()的调用以及程序的突然结束,这也很有用。
某事

1
-据我所知,至少MSVC不会实现此行为。
jalf

9

当将投掷规范添加到语言中时,其意图是最好的,但是实践证明了一种更实用的方法。

对于C ++,我的一般经验法则是仅使用throw规范来指示方法不能抛出。这是有力的保证。否则,假设它会抛出任何东西。


9

好吧,在谷歌搜索该抛出规范时,我看了一下这篇文章:-(http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx

我还在这里复制它的一部分,以便将来无论上面的链接是否起作用都可以使用它。

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

当编译器看到此情况时,使用“ throw()”属性,编译器可以完全优化“ bar”变量,因为它知道无法从MethodThatCannotThrow()引发异常。如果没有throw()属性,则编译器必须创建“ bar”变量,因为如果MethodThatCannotThrow引发异常,则异常处理程序可能/将取决于bar变量的值。

另外,诸如prefast之类的源代码分析工具可以(并且将)使用throw()批注来改善其错误检测功能-例如,如果您有try / catch且您调用的所有函数都标记为throw(),您不需要try / catch(是的,如果您以后调用可能抛出的函数,则会出现问题)。


3

内联函数的不抛出规范只能返回一个成员变量,并且不可能抛出异常,某些编译器可能会使用它们进行悲观化(与优化相反的虚假单词),从而对性能产生不利影响。Boost文献中对此进行了描述:异常规范

对于某些编译器,如果进行了正确的优化,并且对非内联函数的无约束规范可能是有益的,并且对该函数的使用会以合理的方式影响性能。

在我看来,是否使用它是一个非常挑剔的人发出的电话,它是性能优化工作的一部分,也许是使用性能分析工具。

上面链接的引号是针对那些匆忙的人的(包含从天真的编译器指定对内联函数的抛出异常的不良影响的示例):

异常规范的原理

有时会对异常规范[ISO 15.4]进行编码,以指示可能会抛出哪些异常,或者是因为程序员希望它们可以提高性能。但是从智能指针考虑以下成员:

T&运算符*()const throw(){return * ptr; }

该函数不调用其他函数。它仅处理诸如指针之类的基本数据类型,因此,永远无法调用异常规范的运行时行为。该功能完全向编译器公开;实际上,它是声明为内联的。因此,智能编译器可以轻松推断出这些函数无法引发异常,并且可以基于空的异常规范进行相同的优化。但是,“哑”编译器可能会进行各种悲观化。

例如,如果存在异常规范,某些编译器会关闭内联。一些编译器添加了try / catch块。这样的悲观化可能会造成性能灾难,从而使代码在实际应用中无法使用。

尽管起初很吸引人,但是异常规范往往会带来后果,需要仔细思考才能理解。异常规范的最大问题是程序员使用它们时,就像它们具有程序员想要的效果一样,而不是实际上具有效果。

非内联函数是“不抛出任何异常”异常规范的地方,这对于某些编译器可能会有所帮助。


1
“异常规范的最大问题是程序员使用它们时,就像它们具有程序员想要的效果一样,而不是实际上具有的效果。” 与机器或人员进行交流时,这是导致错误的第一大原因:我们所说的与我们所说的之间的区别。
Thagomizer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.