Is there a generally accepted idiom for indicating C++ code can throw exceptions?

混江龙づ霸主 提交于 2019-11-30 05:10:26

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:

  • No-throw guarantee: The function will never throw an exception
  • Strong exception safety guarantee: If an exception is thrown, the object will be left in its initial state.
  • Basic exception safety guarantee: If an exception is thrown, the object be left in a valid state.

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.

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, and throw(...) 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.

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.

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.

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:

So here’s what seems to be the best advice we as a community have learned as of today:

Moral #1: Never write an exception specification.

Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.

Todd Stout

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.

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.

  1. 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.

  2. 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.

  3. 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.

#include <stdexcept>

class MyException: public std::exception
{
    public:
    ~MyException() throw() {}
     char* what() const throw()
     {
        try
        {
            // Do Stuff that may throw
            return "HI";
        }
        // Exceptions must not escape this method
        // So catch everything.
        catch(...)
        {
           return "Bad Stuff HAppening Cant Make Exception Message.";
        }
     }
};

There is a way to specify which exceptions a function can throw in C++. An example taken from the Stroustrup book is

void f(int a) throw (x2, x3);

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!