【问题标题】:Throw keyword in function's signature在函数签名中抛出关键字
【发布时间】:2010-11-06 12:34:42
【问题描述】:

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

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

【问题讨论】:

标签: c++ exception


【解决方案1】:

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

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

【讨论】:

  • 是的。有比 throw (myEx) 更好的向代码添加空格的方法。
  • 是的,刚刚发现异常规范的人通常认为它们像在 Java 中一样工作,编译器能够强制执行它们。在 C++ 中,这不会发生,这使得它们的用处大大降低。
  • 那么文档的目的又如何呢?此外,您将被告知即使您尝试也不会捕获到哪些异常。
  • @AndersLindén 文档的目的是什么?如果您只想记录代码的行为,只需在其上方添加注释即可。不知道你说的第二部分是什么意思。即使您尝试也无法捕获的异常?
  • 我认为代码在记录代码方面非常强大。 (cmets 可以撒谎)。我所指的“文档”效果是您将确定可以捕获哪些异常,而其他异常则不能。
【解决方案2】:

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

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

cmets 是否正确?不完全的。 Gunc() 可能确实会抛出一些东西,Hunc() 很可能会抛出 A 或 B 以外的东西!编译器只是保证在他们这样做时会毫无意义地击败它们……哦,而且大多数时候也会毫无意义地击败你的程序。

归根结底就是这样,您可能最终会调用terminate(),而您的程序会快速而痛苦地死去。

GOTW 的结论是:

因此,到目前为止,我们作为一个社区所学到的似乎是最好的建议:

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

【讨论】:

  • 我不知道为什么我会抛出异常并且无法提及它。即使它是由另一个函数抛出的,我也知道可能会抛出什么异常。我能看到的唯一原因是因为它很乏味。
  • @Ken:关键是编写异常规范会产生负面影响。唯一的积极作用是它向程序员展示了可能发生的异常,但是由于编译器没有以合理的方式对其进行检查,因此很容易出错,因此不值得。
  • 哦,好的,感谢您的回复。我想这就是文档的用途。
  • 不正确。应该编写异常规范,但其想法是传达调用者应尝试捕获的错误。
  • 正如@StudentT 所说:保证不再抛出任何其他异常是函数的责任。如果他们这样做了,程序将按原样终止。声明 throw 意味着处理这种情况不是我的责任,调用者应该有足够的信息来执行此操作。不声明异常意味着它们可以在任何地方发生并且可以在任何地方处理。这当然是反 OOP 的混乱。在错误的地方捕获异常是设计失败。我建议不要抛出空,因为异常是异常的,大多数函数无论如何都应该抛出空。
【解决方案3】:

要为这个问题的所有其他答案增加一点价值,应该在这个问题上投入几分钟: 以下代码的输出是什么?

#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;
    }
}

Answer: 如here 所述,程序调用std::terminate(),因此不会调用任何异常处理程序。

详细说明:首先调用my_unexpected() 函数,但由于它没有为throw_exception() 函数原型重新抛出匹配的异常类型,所以最后调用了std::terminate()。所以完整的输出如下所示:

user@user:~/tmp$ g++ -o except.test except.test.cpp
user@user:~/tmp$ ./except.test
好吧 - 这是出乎意料的
在抛出“int”实例后调用终止
中止(核心转储)

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

    • 注意对 std::unexpected() 的调用通常会导致对 std::terminate() 的调用和程序的突然结束。
    • - 据我所知,MSVC 至少没有实现这种行为。
    【解决方案5】:

    好吧,在谷歌上搜索这个 throw 规范时,我看了这篇文章:- (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(是的,如果您稍后调用可能抛出的函数,这会出现问题)。

    【讨论】:

      【解决方案6】:

      将 throw 规范添加到语言中是出于最好的意图,但实践证明了一种更实用的方法。

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

      【讨论】:

        【解决方案7】:

        内联函数的不抛出规范,它只返回一个成员变量并且不可能抛出异常,一些编译器可能会使用它来执行悲观化(与优化相反的虚构词) 可能对性能产生不利影响。这在 Boost 文献中有所描述:Exception-specification

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

        对我来说,是否使用它听起来像是一个非常挑剔的眼睛发出的调用,作为性能优化工作的一部分,可能使用分析工具。

        来自上述链接的引述,供那些匆忙的人使用(包含一个在幼稚编译器的内联函数上指定 throw 的不良意外影响示例):

        异常规范的基本原理

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

        T& operator*() const throw() { return *ptr; }

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

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

        虽然最初很吸引人,但异常规范往往会产生需要仔细考虑才能理解的后果。异常规范的最大问题是程序员使用它们时好像它们具有程序员想要的效果,而不是它们实际具有的效果。

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

        【讨论】:

        • “异常规范的最大问题是程序员使用它们时好像它们具有程序员想要的效果,而不是它们实际具有的效果。”这是与机器或人交流时出错的第一大原因:我们所说的和我们的意思之间的区别。
        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 2012-02-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-09
        相关资源
        最近更新 更多