c++ 函数签名中的Throw关键字

g2ieeal7  于 11个月前  发布在  其他
关注(0)|答案(7)|浏览(107)

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

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

字符串

yyhrrdl8

yyhrrdl81#

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

noj0wjuj

noj0wjuj2#

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

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

字符串
评论是否正确?不完全是。Gunc()确实可能抛出一些东西,而Hunc()很可能抛出除了A或B之外的东西!编译器只是保证在它们这样做的时候把它们打得毫无意义.哦,而且在大多数情况下也会把你的程序打得毫无意义。
这就是问题的根源,你可能最终会调用terminate(),你的程序会很快但痛苦地死去。
全球技术工作组的结论是:
因此,这似乎是我们作为一个社区到今天为止学到的最好的建议:

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

dzhpxtsq

dzhpxtsq3#

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

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

字符串
答:如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”示例后终止调用
中止(堆芯倾倒)

ilmyapht

ilmyapht4#

throw说明符的唯一实际作用是,如果你的函数抛出了与myExc不同的东西,std::unexpected将被调用(而不是正常的未处理异常机制)。
为了记录函数可以抛出的异常类型,我通常这样做:

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

字符串

zsohkypk

zsohkypk5#

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

kb5ga3dv

kb5ga3dv6#

当抛出规范被添加到语言中时,它的意图是最好的,但实践证明了一种更实用的方法。
对于C++,我的一般经验是只使用抛出规范来表明一个方法不能抛出。这是一个强有力的保证。否则,假设它可以抛出任何东西。

dpiehjr4

dpiehjr47#

对于只返回成员变量而不可能抛出异常的内联函数,某些编译器可能会使用no throw规范来执行 pessimizations(与优化相反的词),这可能会对性能产生不利影响。这在Boost文献中有所描述:
对于 * 某些编译器 ,如果进行了正确的优化,并且该函数的使用以其合理的方式影响性能,则对非内联函数的无抛出规范可能是有益的。
对我来说,是否使用它听起来像是一个非常挑剔的人在性能优化工作中做出的决定,也许是使用分析工具。
上面的链接中的一句话是为那些匆忙的人准备的(包含了一个例子,说明了在一个天真的编译器中对内联函数指定 throw 会产生意想不到的不良影响):
例外规范依据
异常规范[ISO 15.4]有时会被编码来指示可能抛出的异常,或者因为程序员希望它们能提高性能。但是考虑一下智能指针中的以下成员:
T& operator
()const throw(){ return *ptr; }
此函数不调用其他函数;它只操作基本的数据类型,如指针。因此,异常规范的运行时行为永远不会被调用。事实上,它是内联声明的。因此,聪明的编译器可以很容易地推断出函数不能抛出异常,并根据空的异常规范进行同样的优化。然而,一个“哑”编译器可能会进行各种各样的优化。
例如,有些编译器在有异常规范时关闭内联,有些编译器添加try/catch块,这样的伪代码可能是一场性能灾难,使代码在实际应用中无法使用。
异常规范的最大问题是,程序员使用它们时,把它们当作程序员想要的效果,而不是它们实际上的效果。
非内联函数是一个“不抛出任何异常”的地方,这可能对某些编译器有一些好处。

相关问题