在函数的签名中抛出关键字

Throw keyword in function's signature

提问人:Konstantin 提问时间:6/29/2009 最后编辑:Mark LakataKonstantin 更新时间:11/17/2023 访问量:164077

问:

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

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}
C++ 异常

评论

0赞 laalto 6/29/2009
请参阅最近的相关问题:stackoverflow.com/questions/1037575/...
0赞 akauppi 9/11/2009
stackoverflow.com/questions/88573/......
1赞 Aaron McDaid 8/18/2015
有什么变化吗?noexcept
15赞 Anders Lindén 3/28/2016
对与编程相关的事情发表意见并没有错。这个关闭标准很糟糕,至少对于这个问题是这样。这是一个有趣的问题,有有趣的答案。
2赞 François Andrieux 4/6/2018
应该注意的是,自 C++11 起,异常规范已被弃用。

答:

143赞 jalf 6/29/2009 #1

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

http://www.gotw.ca/publications/mill22.htm 更详细地解释了原因,但问题部分是编译器无法强制执行这一点,因此必须在运行时对其进行检查,这通常是不可取的。而且在任何情况下都没有得到很好的支持。(MSVC 忽略异常规范,但 throw() 除外,它将其解释为保证不会引发异常。

评论

27赞 Assaf Lavie 6/29/2009
是的。在代码中添加空格的方法比 throw (myEx) 更好。
4赞 jalf 6/29/2009
是的,刚刚发现异常规范的人通常认为它们的工作方式类似于 Java 中,编译器能够强制执行它们。在C++中,这不会发生,这使得它们变得不那么有用。
11赞 Anders Lindén 3/28/2016
但是,记录的目的呢?此外,即使您尝试,您也会被告知哪些异常永远不会被发现。
1赞 jalf 4/1/2016
@AndersLindén记录的目的是什么?如果您只想记录代码的行为,只需在其上方添加注释即可。不知道你对第二部分的意思。即使你尝试也永远不会发现的异常?
8赞 Anders Lindén 4/1/2016
我认为代码在记录代码时非常强大。(评论可以撒谎)。我所指的“记录性”效果是,您将确定哪些例外可以捕获,而其他例外则不能捕获。
9赞 Greg D 6/29/2009 #2

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

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

14赞 Paolo Tedesco 6/29/2009 #3

throw 说明符的唯一实际效果是,如果函数抛出的内容与 不同,将被调用(而不是正常的未处理异常机制)。myExcstd::unexpected

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

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

评论

5赞 sth 6/29/2009
同样需要注意的是,对 std::unexpected() 的调用通常会导致对 std::terminate() 的调用和程序的突然结束。
1赞 jalf 6/29/2009
- 据我所知,MSVC 至少没有实现这种行为。
66赞 sth 6/29/2009 #4

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

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

评论正确吗?差一点。 可能确实会扔东西,而且很可能会扔 A 或 B 以外的东西!编译器只是保证如果他们这样做,就会毫无意义地击败他们......哦,大多数时候,你的程序也毫无意义。Gunc()Hunc()

这就是归根结底的原因,你可能最终会接到一个电话,你的程序会快速但痛苦地死去。terminate()

GOTW的结论是:

因此,以下是我们作为一个社区迄今为止学到的最好的建议:

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

评论

1赞 MasterMastic 6/9/2013
我不确定为什么我会抛出一个例外并且无法提及它。即使它是由另一个函数抛出的,我也知道会抛出哪些异常。我能看到的唯一原因是因为它很乏味。
4赞 sth 6/9/2013
@Ken:关键是编写异常规范大多会带来负面后果。唯一的积极效果是它向程序员展示了可能发生的异常,但由于编译器没有以合理的方式检查它,因此容易出错,因此价值不大。
1赞 MasterMastic 6/10/2013
哦,好的,谢谢你的回复。我想这就是文档的用途。
2赞 SmallChess 9/23/2016
不正确。应该编写异常规范,但这个想法是传达调用者应该尝试捕获的错误。
1赞 Jan Turoň 2/18/2017
正如@StudentT所说:职能部门有责任保证不会进一步抛出任何其他例外情况。如果他们这样做,程序将按应有的方式终止。声明 throw 意味着处理这种情况不是我的责任,调用者应该有足够的信息来执行此操作。不声明异常意味着它们可以发生在任何地方,并且可以在任何地方处理。这当然是反OOP的混乱。在错误的地方捕获异常是设计失败。我建议不要扔空,因为异常是例外的,大多数函数无论如何都应该扔空。
10赞 Pratik Singhal 7/24/2014 #5

好吧,在谷歌上搜索这个投掷规范时,我看了一下这篇文章:- (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(是的,如果你以后调用一个可以抛出的函数,这就会有问题)。

36赞 John Doe 6/25/2015 #6

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

#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++ -o except.test
except.test.cpp user@user:~/tmp$ ./except.test
好吧 - 这是在抛出“int”实例后意外
终止的
已中止(核心转储)

3赞 Pablo Adames 7/7/2017 #7

某些编译器可能会使用对仅返回成员变量且不可能引发异常的内联函数的无抛出规范进行悲观化(与优化相反的虚构词),这可能会对性能产生不利影响。这在 Boost 文献中进行了描述:异常规范

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

对我来说,这听起来像是是否使用它是一个非常挑剔的眼光,作为性能优化工作的一部分,也许是使用分析工具。

对于那些匆忙的人来说,上面链接中的一句话(包含一个示例,说明从朴素的编译器中指定对内联函数进行抛掷的不良意外影响):

例外规范理由

异常规范 [ISO 15.4] 有时被编码以指示可能引发的异常,或者因为程序员希望它们能够提高性能。但请考虑智能指针中的以下成员:

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

此函数不调用其他函数;它只操作基本数据类型,如指针 因此,永远不能调用异常规范的运行时行为。该函数完全暴露给编译器;事实上,它是内联声明的,因此,智能编译器可以很容易地推断出函数无法抛出异常,并根据空的异常规范进行相同的优化。然而,一个“愚蠢”的编译器可能会做出各种悲观情绪。

例如,如果存在异常规范,某些编译器会关闭内联。一些编译器会添加 try/catch 块。这种悲观情绪可能是性能灾难,使代码在实际应用中无法使用。

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

非内联函数是“不抛出任何内容”异常规范可能对某些编译器有一些好处的地方。

评论

1赞 Thagomizer 5/18/2019
“异常规范的最大问题在于,程序员使用它们时,就好像它们具有程序员想要的效果,而不是它们实际具有的效果。这是与机器或人交流时出错的#1原因:我们所说的和我们的意思之间的区别。