悲伤的 C++ throw(…)

当在C++语言中引入异常时,引入了相应的throw(…)动态异常说明符,注释了哪些异常可以由函数抛出。比如:

// this function might throw an integer or a pointer to char,
// but nothing else.
void foo() throw(int, char*);

 这让很多人非常愤怒,并被普遍认为是一个糟糕的举动。

根据C++ 98标准,如果函数抛出了未在其动态异常说明符中指定的类型中列出的异常, 系统调用了std::unexpected()函数,std::unexpected()的默认行为是通过调用std::terminate()来终止程序。作为特殊情况,throw()意味着函数根本不应该抛出任何异常。
在C++ 11标准里,放弃了throw(…)动态异常说明符,并且在C++ 17中,除了throw()的特殊情况之外,所有对动态异常说明符的支持都被删除。 同时,改变了当你说你不会的时候抛出异常的惩罚:运行时直接调用std::terminate(),而不是通过std::unexpected()。
当然,微软C++编译器必须做一些不同的事情。

微软C++编译器将throw(…)异常说明符视为程序员的一个允诺,但没有强制执行。它相信你会遵守你自己强加的合同。如果在函数承诺不抛出异常时抛出异常,则行为是未定义的。如果函数说可以抛出一些异常,编译器不会验证是否允许实际抛出的异常;它只是传播异常。实际上,发生的情况是编译器在假定不会引发不允许的异常的情况下执行优化。最常见的这种优化是,编译器不必为它“知道”永远不需要展开的事情注册展开代码,因为在对象销毁之前,没有可能抛出异常的点。

void Example()
{
   ObjectWithDestructor obj;
   obj.stuff_that_does_not_throw();
   // destructor runs here
}

如果stuff_that_does_not_throw被标记为不抛出,那么编译器可以避免在异常传播期间注册obj进行展开,因为您承诺任何异常都不能逃逸。然后抛出异常并使所有优化无效。最常见的可见效果是,从不应该让异常转义的函数传播的异常,以及某些对象析构函数无法运行。

但是等等,一切都没有失去。如果启用/std:c++17,那么微软C++编译器将实现throw(…)的标准行为。

 

 是的,到那儿花了很长时间,但迟到总比不到好。

posted on 2019-11-08 15:23  活着的虫子  阅读(2688)  评论(0编辑  收藏  举报

导航