是否有一个普遍接受的习语来表示 C++ 代码可以抛出异常吗?
我在使用 C++ 代码时遇到过问题,调用者意外地抛出异常。 读取您正在使用的模块的每一行来查看它是否抛出异常以及如果抛出异常,则并不总是可行或不切实际的。
是否存在处理此问题的既定惯用语或“最佳实践”?
我想到了以下内容:
在我们的 doxygen 文档中,我们可以在每个预期抛出异常的函数及其类型中添加注释。
- 优点:简单。
- 缺点:容易出现用户错误。
为了安全起见,我们可以在应用程序范围内使用
try/catch(...)
。- 优点:我们不会再出现任何未捕获的异常。
- 缺点:异常是在距离抛出很远的地方捕获的。 很难弄清楚该做什么或出了什么问题。
使用异常规范
- 优点:这是处理此问题的语言认可的方法。
- 缺点:需要重构问题库才能有效。 在编译时不强制执行,因此违规会变成运行时问题,这正是我试图避免的!
这些方法有什么经验,或者我不知道的任何其他方法?
I have seen problems when using C++ code that, unexpectedly to the caller, throws an exception. It's not always possible or practical to read every line of a module that you are using to see if it throws exceptions and if so, what type of exception.
Are there established idioms or "best practices" that exist for dealing with this problem?
I've thought of the following:
In our doxygen documentation, we could add a comment in every function that is expected to throw an exception and it's type(s).
- Pluses: Simple.
- Minuses: Subject to user error.
We could have an app-wide
try/catch(...)
for safety.- Pluses: We won't have any more uncaught exceptions.
- Minuses: The exception is caught far away from the throw. It's hard to figure out what to do or what went wrong.
Use Exception Specifications
- Pluses: This is the language-sanctioned way of dealing with this problem.
- Minuses: Refactoring of problem libraries needed for this to be effective. Not enforced at compile-time, so violations turn into run-time problems, which is what I'm trying to avoid!
Any experiences with these methods, or any additional methods that I'm unaware of?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
对标题问题的简短回答 - 表示函数可以抛出的惯用语不是来记录它“这个函数不会抛出”。 也就是说,默认情况下一切都可以抛出。
C++ 不是 Java,并且没有编译器检查的异常。 C++ 中没有任何东西可以让编译器告诉您您的代码声称它不会抛出,但调用了可能会抛出的东西。 所以你不能完全避免这是一个运行时问题。 静态分析工具可能会有所帮助,但不确定。
如果您只关心 MSVC,则可以考虑在不抛出异常的函数上使用空异常规范或
__declspec(nothrot)
,并在不抛出异常的函数上使用throw(...)
的功能。 这不会导致代码效率低下,因为 MSVC 不会发出代码来检查声明为 nothrow 的函数实际上不会抛出。 GCC 可以使用-fno-enforce-eh-specs
执行相同的操作,请检查您的编译器文档。 然后一切都将被自动记录。选项 2,应用程序范围的 try-catch 并不是真正的“为了安全”,这只是因为你认为你可以用异常做一些更有用的事情(比如打印出一些东西并干净地退出),而不仅仅是让 C++ 运行时调用 <代码>终止。 如果您在编写代码时假设某件事不会抛出,而实际上确实发生了,那么您可能在其中任何一个实际发生之前就已经未定义,例如,如果析构函数对一致状态做出了错误的假设。
我通常会做(1)的变体:对于每个函数记录它提供的异常保证 - 不抛出、强、弱或无。 最后一个是一个错误。 第一个很有价值但很少见,并且只有交换函数和析构函数才严格需要良好的编码。 是的,它可能会出现用户错误,但任何带有异常的 C++ 编码方式都会出现用户错误。 然后,如果它可以帮助您执行(1),那么还可以执行(2)和/或(3)。
Symbian 有一个预标准的 C++ 方言,有一个称为“leave”的机制,在某些方面类似于异常。 Symbian 中的约定是任何可能离开的函数都必须以 L 结尾命名:
CreateL
、ConnectL
等。平均而言,这会减少用户错误,因为您可以更容易地看到您是否正在调用可能会离开的东西。 正如您所料,讨厌它的人就像讨厌应用程序匈牙利表示法一样,如果几乎所有功能都离开它,它就不再有用。 正如您所期望的,如果您确实编写了一个名称中没有 L 的函数,那么在您找出问题之前,它可能会在调试器中等待很长时间,因为您的假设使您远离了实际的错误。Short answer to the title question - the idiom to indicate a function can throw is not to document it "this function doesn't throw". That is, everything can throw by default.
C++ is not Java, and doesn't have compiler-checked exceptions. There is nothing in C++ which will allow the compiler to tell you that your code claims it won't throw, but calls something which might. So you can't completely avoid this being a runtime problem. Static analysis tools might help, not sure.
If you only care about MSVC, you could consider using an empty exception specification or
__declspec(nothrow)
on functions which don't throw, andthrow(...)
on functions which do. This will not result in inefficient code, because MSVC doesn't emit the code to check that functions declared nothrow actually don't throw. GCC can do the same with-fno-enforce-eh-specs
, check your compiler documentation. Everything will then be automatically documented too.Option 2, the app-wide try-catch isn't really "for safety", it's just because you think you can do something more useful with the exception (like print something out and exit cleanly) than just let the C++ runtime call
terminate
. If you're writing code on the assumption that something won't throw, and it actually does, then you may have gone undefined before either one of them actually happens, for example if a destructor makes a false assumption of consistent state.I'd normally do a variant of (1): for each function document what exception guarantee it offers - nothrow, strong, weak, or none. The last is a bug. The first is prized but rare, and with good coding is only strictly necessary for swap functions and destructors. Yes, it's subject to user error, but any means of C++ coding with exceptions is subject to user error. Then on top of that, also do (2) and/or (3) if it helps you enforce (1).
Symbian has a pre-standard dialect of C++, with a mechanism called "leave" which is like exceptions in some respects. The convention in Symbian is that any function which might leave must be named with an L at the end:
CreateL
,ConnectL
, etc. On average this reduces user error, because you can see more easily whether you're calling something which might leave. As you might expect, the same people hate it who hate apps Hungarian notation, and if almost all functions leave it ceases to be useful. And as you might expect, if you do write a function which leaves without an L in the name, it can be good long while in the debugger before you figure out the problem, because your assumptions point you away from the actual bug.解决问题的惯用方法不是指示您的代码可以抛出异常,而是在您的对象中实现异常安全。 该标准定义了对象应实现的几个异常保证:
当然,该标准记录了每个标准库类的异常安全级别。
这确实是 C++ 中处理异常的方法。 不要标记哪些代码可以或不能抛出异常,而是使用 RAII 来确保对象得到清理,并考虑在 RAII 对象中实现适当级别的异常安全,这样它们就能够在以下情况下生存而无需特殊处理:抛出异常。
只有当异常允许您的对象处于无效状态时,异常才会真正引起问题。 那绝对不应该发生。 您的对象应该始终至少实现基本保证。 (实现提供适当级别的异常安全性的容器类是一项具有启发性的 C++ 练习;))
至于文档,当您能够确定函数可能抛出的某些异常时,通过无论如何,请随意记录它。 但一般来说,当没有指定其他内容时,就假定函数可能会抛出异常。 空抛出规范有时用于记录函数何时从不抛出。 如果不存在,则假设该函数可能会抛出异常。
The idiomatic way to solve the problem is not to indicate that your code can throw exceptions, but to implement exception safety in your objects. The standard defines several exception guarantees objects should implement:
And of course, the standard documents the level of exception safety for every standard library class.
That's really the way to deal with exceptions in C++. Rather than marking which code can or can not throw exceptions, use RAII to ensure your objects get cleaned up, and put some thought into implementing the appropriate level of exception safety in your RAII objects, so they're able to survive without special handling if an exception is thrown.
Exceptions only really cause problems if they allow your objects to be left in an invalid state. That should never happen. Your objects should always implement at least the basic guarantee. (and implementing a container class which provides the proper level of exception safety is an enlightening C++ exercise ;))
As for documentation, when you're able to determine for certain which exceptions a function may throw, by all means feel free to document it. But in general, when nothing else is specified, it is assumed that a function may throw. The empty throw specfication is sometimes used to document when a function never throws. If it's not there, assume that the function may throw.
坦率地说,几乎任何 C++ 函数都可能引发异常。 您不必太担心记录这一点,而是通过使用 RAII 等习惯用法来确保代码异常安全。
Frankly, just about any C++ function can throw an exception. You should not worry too much about documenting this, but instead make your code exception safe, by using idioms such as RAII.
我用的是1和2。
关于1:
您无法避免或阻止用户错误。 如果您认识没有将 doxygen 正确写入纸浆的开发人员,您可以击败他。 但你无法避免或阻止用户错误,所以放弃偏执吧。 如果用户犯了错误,那是他犯的,而不是你犯的。
关于2:
C# 内置了一种捕获未处理异常的方法。 所以这并不是一件“坏事”,尽管我同意它有味道。 有时,崩溃比不一致运行更好,但我做了一个练习,记录任何未处理的异常,然后崩溃。 这允许人们向我发送日志,以便我可以检查堆栈跟踪并找出问题。 这样,每次修正后,崩溃的发生就会越来越少。
I use both 1 and 2.
About 1:
You cannot avoid or prevent user error. You can beat the developer that did not write the doxygen properly to a pulp, if you know him. But you cannot avoid or prevent user error, so drop the paranoia. If the user err'ed, he did it, not you.
About 2:
C# has built-in a way to catch unhandled exceptions. So it is not a 'bad thing', though I agree it smells. Sometimes, it is just best to crash than to run inconsistently, but I made a practice to log any unhandled exceptions and THEN crash. That allows people to send me the log so I can check the stack trace and track down the problem. This way, after each correction, less and less crashes happen.
文档似乎是我所知道的唯一合理的方法。
关于异常规范,这是 Herb Sutter 撰写的一篇关于该主题的旧文章 (2002) http://www.ddj .com/cpp/184401544 它讨论了为什么该语言的这一特性不能为我们提供编译时安全性,并最终得出结论:
Documentation seems to be the only reasonable method that I know of.
Regarding exception specifications, here's an old (2002) article by Herb Sutter on the subject http://www.ddj.com/cpp/184401544 It discusses why this feature of the language doesn't give us compile-time safety and ends up with the conclusion:
C++ 在 c++11 之前定义了抛出规范。 请参阅此问题。 根据我过去的经验,微软编译器会忽略 C++ 抛出规范。 “检查异常”的整个概念是有争议的,尤其是在 Java 领域。 许多开发人员认为这是一次失败的实验。
C++, prior to c++11, defined a throw specification. See this question. It has been my experience in the past that microsoft compilers ignore the C++ throw spec. The whole notion of "checked exceptions" is controversial, especially in the Java realm. Many developers consider it a failed experiment.
使用 doxygen 文档来描述您的方法。 当您使用它们时,您需要检查此文档以了解它们的参数是什么以及它们抛出哪些异常。
编写单元测试以在抛出异常的情况下测试您的代码。
Use doxygen documentation to describe your methods. When you use them, you will need to check this documentation to see what their parameters are, and what exceptions they throw.
Write unit tests to exercise your code in the cases exceptions are thrown.
记录异常安全的级别功能保证。 正如史蒂夫·杰索普(Steve Jessop)在回答中指出的那样,有很多层次。 理想情况下,如果它们全部记录在接口中,或者至少有必要的最低限度:a)从不抛出或b)可能抛出
阅读 亚伯拉罕异常安全保证,由 赫伯·萨特。
我强烈怀疑它在大型代码库中是否实用。 对于远离抛出和很难弄清楚该做什么的问题,一个好的规则是只在你想要处理异常的地方捕获,否则就让它处理冒泡。 一旦异常出现就立即捕获并消除它们并不是一个好主意,仅仅因为……它们是异常,所以你觉得你必须对其做点什么。 在复杂的系统中,最好有日志机制,这样更容易跟踪问题。
别这么做。 阅读 Herb Sutter 的异常规范的实用视角及相关文章。
Document what level of exception safety a function guarantees. As Steve Jessop pointed in his answer, there is number of levels. Ideally if they are documented for interfaces all, or at least necessary minimum: a) never throws or b) may throw
Read about Abrahams exception safety guarantees explained by Herb Sutter.
I strongly doubt it would be practical in a big codebase. Regarding far away from the throw and it's hard to figure out what to do concerns, a good rule is to catch only in places where you want deal with exception, otherwise let it bubble up. It is not a good idea to catch and extinguish exceptions as soon as they appear, just because...they are exceptions, so you feel you have to do something with it. In complex systems, it's good to have a logging mechanism, so it's easier to trace a problem.
Don't do it. Read Herb Sutter's A Pragmatic Look at Exception Specifications and related articles.
您应该始终期待异常并处理它们。 不幸的是,计算机没有自动方式为您进行尽职调查。
You should always expect exceptions and handle them. Unfortunately there's no automated way for the computer to do due diligence for you.
编号
唯一常见的事情是表明你不要扔任何东西。
然后您还应该手动确保没有异常可以真正逃脱您的方法/函数。 注意:如果异常确实逃逸了该方法,则应用程序将被终止。
No.
The only common thing is to indicate you dont throw anything.
And then you should also manually make sure that no exception can actually escape your method/function. Note: If exceptions do escape the method the application will be terminated.
在 C++ 中,有一种方法可以指定函数可以抛出哪些异常。 Stroustrup 书中的一个示例
指定 f 只能抛出 x2 或 x3 类型或派生类型的异常。 使用此语法,您可以轻松查看函数声明,以了解合法允许抛出哪些异常,并进行相应的编码。
There is a way to specify which exceptions a function can throw in C++. An example taken from the Stroustrup book is
which specifies that f can only throw exceptions of type x2 or x3, or derived types. Using this syntax you can easily look at the functions declaration to see what exceptions it is legally allowed to throw, and code accordingly.