【问题标题】:Questions on Exception Specification and Application Design关于异常规范和应用程序设计的问题
【发布时间】:2012-06-15 13:25:11
【问题描述】:

虽然这个话题已经在 SO 上进行了广泛的讨论,但考虑到以下事实,我想澄清一些我仍然不清楚的事情:

  1. 10 年前,Herb Sutter 告诉我们refrain from using this functionality

  2. 指定函数/方法可能抛出的可能异常不会强制编译器在您决定更改函数体并抛出新类型的异常时对您大喊大叫,错误地忘记更改异常规范在函数的声明中。

  3. 1234563第一个函数可能抛出的错误,这个列表必须包括内部函数可能抛出的所有异常等等,从而在高级和低级函数之间产生紧密耦合,这是非常不可取的。另一方面,我们从 std::runtime_error 派生所有异常,we know is a good practice 并且我们可以指定高级函数只是抛出 std::runtime_error 并完成它。但是等一下……我们实际上在哪里捕获异常?当高级函数应该只抛出 std::runtime_error 时,将对这些高级函数之一的调用包含在 try / catch 块中会不会很奇怪/讨厌/不好,该块会捕获 MyVerySpecific 异常? ?在较低级别的函数中捕获特定异常是否有好处,这些函数无法对它们做任何事情,而是将它们传递到更通用的容器中,并附加更多信息?我当然不想在我编写的每个函数中都编写 try / catch 块,只是为了格式化异常。这就像要求每个函数都验证其参数一样,当人们需要更改低级函数中的某些内容时,这会让人发疯。

问题:

  1. Herb Sutter 关于异常规范的咆哮今天仍然成立吗?从那以后有什么改变吗?我最感兴趣的是 pre-C++0x 标准。如果是,我想我们可以考虑关闭这个话题。

  2. 由于编译器似乎大多忽略了这些异常规范,并且在捕获异常时,在 99% 的情况下会使用catch (const CustomException &ex),那么如何指定函数抛出 CustomException? throw(CustomExecption)throw (CustomException &)throw (const CustomException &)?我已经看到了所有的变化,虽然我会选择第一个,但其他的有什么意义/增加任何好处吗?

  3. 如何实际使用此功能,同时避免上述第三个事实中说明的谬误?

  4. 编辑:假设我们正在构建一个库。如果我们不使用异常规范,它的用户将如何知道预期的异常?他们肯定不会看到 API 方法会在内部调用哪些函数...

【问题讨论】:

  • 现在和当时一样,异常规范已在 C++11 中明确弃用。取而代之的是noexcept,这与这类系统一样健全。
  • 感谢您的拼写更正:D 我根据您的评论添加了一个附加问题。
  • 那么,如何使用该库?就个人而言(您可能会觉得这有点自虐),但我一直认为通用库应该有一个简单的旧 C 接口,其中包含结构和枚举,仅此而已。返回错误代码而不是抛出异常。当然,用 C++ 使用它很痛苦,但它使得在任何其他语言中使用它都变得微不足道。然后他们可以浏览错误代码列表,看看会发生什么。这也让您可以在内部使用您喜欢的任何疯狂的编程风格。只是一个想法。
  • 虽然这听起来不错,但老实说,我不愿意实施它。我认为我们应该指导每个人做得更好,而不是永远传播这些旧的做法......想想这让我畏缩,让我想起我写void main ()时的“美好时光”一切,因为那时我不知道更好......
  • 具有 C 接口的 DLL 可以使用合适的符号提取器轻松检查其功能。我可以使用(比如说)C# p/invoke 直接与它交谈,而无需额外的粘合逻辑。我不需要知道它是用什么语言编写的,也不需要知道它对异常的作用。这些都是好事。如果您正在编写一个专门的仅 C++ 库,那么您当然也可以端到端使用 C++。在这种情况下,您不妨将文档和自定义异常类粘贴到共享头文件中。

标签: c++ design-patterns exception exception-handling try-catch


【解决方案1】:

1/ Herb Sutter 关于异常规范的咆哮今天仍然成立吗?从那以后有什么改变吗?我最感兴趣的是 pre-C++0x 标准。如果是,我想我们可以考虑关闭这个话题。

是的,他们仍然持有。

例外规范是:

  • 中途实现(例如函数指针未指定异常)
  • 在编译时未检查,但在运行时导致终止!!

一般来说,我会反对异常规范,因为它会导致实现细节的泄漏。看看Java异常的状态...

尤其是在 C++ 中?异常规范就像是在踢自己的脚,因为文档中最微小的错误可能会导致std::terminate 调用。请注意,例如,几乎所有函数都可能抛出 std::bad_allocstd::out_of_range

注意:自 C++11 以来,throw() 已被弃用,现在在 C++17 中它已不复存在;相反,从 C++17 开始,可以使用 noexcept(false) 说明符。它在函数指针中得到更好的支持,但仍会导致在运行时终止,而不是在编译时出错。


2/ 由于编译器似乎大多忽略了这些异常规范,并且在捕获异常时,在 99% 的情况下,都会使用 catch (const CustomException &ex),那么如何指定函数抛出 CustomException? throw(CustomExecption) 还是 throw (CustomException &) 还是 throw (const CustomException &)?我已经看到了所有的变化,虽然我会选择第一个,但其他的有什么意义/增加任何好处吗?

编译器不会忽略异常规范,它会设置非常警惕的看门狗(哪些轴)以确保在您遗漏某些内容时终止您的程序。


3/ 如何实际使用此功能,同时避免上述第三个事实中说明的谬误?

如果它保持非正式,您的客户会很感激,所以最好的例子是:

void func(); // throw CustomException

这使您可以专注于重要的异常,并让“不重要”的异常溜走。如果消费者想要它们全部? catch(std::exception const& e) 有效。


4/ 编辑:假设我们正在构建一个库。如果我们不使用异常规范,它的用户将如何知道预期的异常?他们肯定不会看到 API 方法会在内部调用哪些函数...

他们必须这样做吗?

记录重要的事情,std::exception... 处理意外情况。

【讨论】:

  • 感谢 cmets。我会将您的建议整合到我正在开发的库中。
  • std::bad_alloc 是异常规范通常失败的一个很好的例子
  • 在 C++1 中,“中途实现(例如函数指针未指定异常)”的问题消失了。在 clang 3.8.0 中尝试此代码。 int main() { void (*p)() throw(int);无效(* p1)()抛出(); //p1= p; }
  • 在 C++17 中,“中途实现(例如函数指针未指定异常)”的问题消失了。更重要的是,分配检查兼容性。您可以在 clang 3.8.0 中尝试此代码。 int main() { void (*p)() throw(int);无效(* p1)()抛出(); //p1= p; }
【解决方案2】:
  1. Herb Sutter 关于异常规范的咆哮今天仍然成立吗?从那以后有什么变化吗?

我不会称之为咆哮。他只是指出了与异常规范相关的问题。

是的,它仍然成立。如文中所述,如果抛出未指定的异常,程序将终止,这对于 99% 的应用程序来说是不可接受的。

  1. 如何指定函数抛出 CustomException?
class A
{
 //...
   void foo() throws( CustomException );
};
  1. 如何实际使用此功能,同时避免上述第三个事实中说明的谬误?

通过查看函数声明,用户知道可以抛出哪些异常。问题是当需要抛出新异常时,所有函数声明都需要更改。

  1. 假设我们正在构建一个库。如果我们不使用异常规范,它的用户如何知道会出现哪些异常?

通过阅读文档。

【讨论】:

  • 但这仍然容易出错,因为我真的不能指望有人弄清楚对(低级)函数的一个小的更改会传播到哪里,以便可以相应地修改异常文档。老实说,这似乎是一团糟。
  • @MihaiTodor:这是一团糟,这就是最好避免的原因。 C++11 弃用异常规范(除了新的noexcept)是有原因的。
猜你喜欢
  • 2020-03-22
  • 2010-12-08
  • 1970-01-01
  • 1970-01-01
  • 2020-01-14
  • 2013-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多