33

I'm trying to convince my team lead to allow using exceptions in C++ instead of returning a bool isSuccessful or an enum with the error code. However, I can't counter this criticism of his.

Consider this library:

class OpenFileException() : public std::runtime_error {
}

void B();
void C();

/** Does blah and blah. */
void B() {
    // The developer of B() either forgot to handle C()'s exception or
    // chooses not to handle it and let it go up the stack.
    C();
};

/** Does blah blah.
 *
 * @raise OpenFileException When we failed to open the file. */
void C() {
    throw new OpenFileException();
};
  1. Consider a developer calling the B() function. He checks its documentation and sees that it returns no exceptions, so he doesn't try to catch anything. This code could crash the program in production.

  2. Consider a developer calling the C() function. He doesn't check the documentation so doesn't catch any exceptions. The call is unsafe and could crash the program in production.

But if we check for errors in this way:

void old_C(myenum &return_code);

A developer using that function will be warned by the compiler if he doesn't provide that argument, and he'd say "Aha, this returns an error code I must check for."

How can I use exceptions safely, so that there's some sort of contract?

21
  • 4
    @Sjoerd The main advantage is that a developer is forced by the compiler to provide a variable to the function to store the return code; he is made aware that there can be an error and he should handle it. That is infinitely better than exceptions, which have no compile time checking at all.
    – DBedrenko
    Oct 28, 2016 at 9:55
  • 4
    "he should handle it" - there is no checking whether this happens either.
    – Sjoerd
    Oct 28, 2016 at 10:04
  • 4
    @Sjoerd It's not necessary. It's good enough to make the developer aware that an error can occur and that they should check for it. It's in the plain sight of the reviewers, too, whether they checked for an error or not.
    – DBedrenko
    Oct 28, 2016 at 10:19
  • 5
    You might have a better chance using an Either/Result monad to return the error in a type-safe composable way
    – Daenyth
    Oct 28, 2016 at 11:27
  • 5
    @gardenhead Because too many developers don't use it properly, end up with ugly code, and proceed to blame the feature instead of themselves. The checked exception feature in Java is a beautiful thing, but it gets a bad rap because some people just don't get it. Oct 29, 2016 at 0:16

5 Answers 5

46

This is a legitimate criticism of exceptions. They are often less visible than simple error handling such as returning a code. And there is no easy way to enforce a "contract". Part of the point is to enable you to let exceptions be caught at a higher level (if you have to catch every exception at every level, how different is it from returning an error code, anyway?). And this means that your code could be called by some other code that doesn't handle it appropriately.

Exceptions do have downsides; you have to make a case based on cost-benefit.
I found these two articles helpful: The necessity of exceptions and Everything wrong with exceptions. Also, this blog post offers opinions of many experts on exceptions, with a focus on C++. While expert opinion seems to lean in favor of exceptions, it is far from a clear consensus.

As for convincing your team lead, this might not be the right battle to pick. Especially not with legacy code. As noted in the second link above:

Exceptions cannot be propagated through any code which is not exception safe. The use of exceptions thus implies that all code in the project must be exception safe.

Adding a little bit of code which uses exceptions to a project that mainly does not is probably not going to be an improvement. Not using exceptions in otherwise well-written code is far from a catastrophic problem; it might not be a problem at all, depending on the application and which expert you ask. You have to pick your battles.

This is probably not an argument I would spend effort on--at least not until a new project is started. And even if you have a new project, is it going to use or be used by any legacy code?

4
  • 1
    Thanks for the advice and the links. This is a new project, not a legacy one. I'm wondering why exceptions are so widespread when they have such a big drawback that makes their use unsafe. If you catch an exception n-levels higher, then later you modify the code and move it around, there's no static checking to guarantee the exception is still caught and handled (and it's too much to ask reviewers to check all functions n-levels deep).
    – DBedrenko
    Oct 28, 2016 at 9:48
  • 3
    @Dee see the links above for some arguments in favor of exceptions (I've added an additional link with more expert opinion).
    – user82096
    Oct 28, 2016 at 10:40
  • 6
    This answer is spot on!
    – Doc Brown
    Oct 28, 2016 at 13:50
  • 1
    I can state from experience that trying to add exceptions to a pre-existing codebase is a REALLY bad idea. I've worked with such a codebase and it was REALLY, REALLY hard to work with because you never knew what was going to blow up in your face. Exceptions should ONLY be used in projects where you plan for them up front. Oct 31, 2016 at 13:43
26

There are literally whole books written on this subject, so any answer will be a summary at best. Here are some of the important points I think are worth making, based on your question. It's not an exhaustive list.


Exceptions are intended NOT to be caught all over the place.

As long as there is a general exception handler in the main loop - depending on the type of application (web server, local service, command line utility ...) - you usually have all the exception handlers you need.

In my code, there are only a few catch statements - if any at all - outside the main loop. And that seems to be the common approach in modern C++.


Exceptions and return codes are not mutually exclusive.

You should not make this an all-or-nothing approach. Exceptions should be used for exceptional situations. Things like "Config file not found," "Disk Full" or anything else that cannot be handled locally.

Common failures, like checking whether a filename provided by the user is valid, is not an use case for exceptions; Use a return value in those cases instead.

As you see from the above examples, "file not found" can be either an exception or a return code, depending on the use case: "is part of the installation" versus "user can make a typo."

So there is no absolute rule. A rough guideline is: if it can be handled locally, make it a return value; if you cannot handle it locally, throw an exception.


Static checking of exceptions is not useful.

As exceptions are not to be handled locally anyway, it usually isn't important which exceptions can be thrown. The only useful information is whether any exception can be thrown.

Java has static checking, but it usually is considered a failed experiment, and most languages since - notably C# - don't have that type of static checking. This is a good read about the reasons why C# doesn't have it.

For those reasons, C++ has deprecated its throw(exceptionA, exceptionB) in favor of noexcept(true). The default is that a function can throw, so programmers should expect that unless the documentation explicitly promises otherwise.


Writing exception safe code has nothing to do with writing exception handlers.

I would rather say that writing exception safe code is all about how to avoid writing exception handlers!

Most of the best practices intend to reduce the number of exception handlers. Writing code once and automatically invoke it - e.g. through RAII - results in less bugs than copy-pasting the same code all over the place.

18
  • 3
    "As long as there is a general exception handler...you are usually ok already." This contradicts most sources, which stress that it is very challenging to write exception safe code.
    – user82096
    Oct 28, 2016 at 10:43
  • 12
    @dan1111 "Writing exception safe code" has little to do with writing exception handlers. Writing exception safe code is about using RAII to encapsulate resources, using "do all work locally first then use swap/move," and other best practices. Those are challenging when you're not used to them. But "writing exception handlers" is not part of those best practices.
    – Sjoerd
    Oct 28, 2016 at 10:47
  • 4
    @AxiomaticNexus On some level, you can explain away every unsuccessful usage of a feature as "bad developers." The greatest ideas are the ones that reduce bad usage because they make doing the right thing simple. Statically checked exceptions do not fall in that category. They create work disproportionate to their value, often in places where the code being written simply does not even need to care about them. Users are tempted to silence them in wrong ways to make the compiler stop bothering them. Even done right, they lead to a lot of clutter.
    – jpmc26
    Oct 28, 2016 at 21:19
  • 4
    @AxiomaticNexus The reason that it is disproportionate is that this can easily propagate to nearly every function in your entire code base. When all the functions in half my classes access the database, not every one of them needs to explicitly declare it can throw a SQLException; neither does every controller method that calls them. That much noise is actually a negative value, regardless of how small the amount of work is. Sjoerd hits the nail on the head: most of your code does not need to be concerned with exceptions because it can't do anything about them anyway.
    – jpmc26
    Oct 28, 2016 at 21:45
  • 3
    @AxiomaticNexus Yes, because the best developers are so perfect their code will always handle every single possible user input perfectly, and you will never, ever, ever see those exceptions in prod. And if you do, it means you are a failure at life. Seriously, don't give me this garbage. Nobody is that perfect, not even the best. And your app should behave sanely even in the face of those errors, even if "sanely" is "stop and return a half decent error page/message". And guess what: that's the same thing you do with 99% of IOExceptions, too. The checked division is arbitrary and useless.
    – jpmc26
    Oct 28, 2016 at 22:27
8

C++ programmers don't look for exception specifications. They look for exception guarantees.

Suppose a piece of code did throw an exception. What assumptions can the programmer make that will still be valid? From the way the code is written, what does the code guarantee in the aftermath of an exception?

Or is it possible that a certain piece of code can guarantee never to throw (i.e. nothing short of the OS process being terminated)?

The word "roll back" occurs frequently in discussions about exceptions. Being able to roll back to a valid state (that is explicitly documented) is an example of exception guarantee. If there is no exception guarantee, a program should be terminating on the spot because it is not even guaranteed that any code it executes thereafter will work as intended - say, if memory has corrupted, any further operation is technically undefined behavior.

Various C++ programming techniques promote exception guarantees. RAII (scope-based resource management) provides a mechanism to execute cleanup code and ensure that resources are released in both normal cases and exceptional cases. Making a copy of data before performing modifications on objects allow one to restore that object's state if the operation fails. And so on.

The answers to this StackOverflow question gives a glimpse to the great lengths C++ programmers go to understand all of the possible failure modes that could happen to their code and try to guard the validity of program state despite failures. Line-by-line analysis of C++ code becomes a habit.

When developing in C++ (for production use), one cannot afford to gloss over the details. Also, binary blob (non-open-source) is the bane of C++ programmers. If I have to call some binary blob, and the blob fails, then reverse engineering is what a C++ programmer would do next.

Reference: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety - see under Exception Safety.

C++ had a failed attempt at implementing exception specifications. Later analysis in other languages says that exception specifications simply aren't practical.

Why it is a failed attempt: to enforce it strictly, it has to be part of the type system. But it isn't. The compiler does not check for exception specification.

Why C++ chose that, and why experiences from other languages (Java) proves that exception specification is moot: As one modify the implementation of a function (for example, it needs to make a call to a different function that may throw a new kind of exception), a strict exception specification enforcement means you have to update that specification as well. This propagates - you may end up having to update the exception specifications for dozens or hundreds of functions for what is a simple change. Things get worse for abstract base classes (the C++ equivalent of interfaces). If exception specification is enforced on interfaces, implementations of interfaces will not be allowed to call functions that throw different types of exceptions.

Reference: http://www.gotw.ca/publications/mill22.htm

Starting with C++17, the [[nodiscard]] attribute can be used on function return values (see: https://stackoverflow.com/questions/39327028/can-a-c-function-be-declared-such-that-the-return-value-cannot-be-ignored).

So if I make a code change and that introduces a new kind of failure condition (i.e. a new type of exception), is it a breaking change? Should it have compelled the caller to update the code, or at least be warned about the change?

If you accept the arguments that C++ programmers look for exception guarantees instead of exception specifications, then the answer is that if the new kind of failure condition doesn't break any of the exception guarantees the code previously promises, it is not a breaking change.

5
  • "As one modify the implementation of a function (for example, it needs to make a call to a different function that may throw a new kind of exception), a strict exception specification enforcement means you have to update that specification as well" - Good, as you should. What would be the alternative? Ignoring the newly introduced exception? Oct 28, 2016 at 19:51
  • @AxiomaticNexus That is also answered by exception guarantee. In fact I argue specification can never be complete; guarantee is what we look for. Often, we compare the exception type in an attempt to find out what guarantee was broken - in order to guess at what guarantee was still valid. Exception specification lists some of the types you need to check, but remember that in any practical system, the list cannot be possibly complete. Most of the time, it forces people to take the shortcut of "eating" the exception type, wrapping it in another exception, which makes exception type checking hard
    – rwong
    Oct 28, 2016 at 20:02
  • @AxiomaticNexus I am neither arguing for or against the use of exception. Typical exception usage encourages having a base exception class and some derived exception classes. Meanwhile, one has to remember that other exception types are possible, e.g. those thrown from C++ STL or other libraries. It could be argued that exception specification could be made to work in this case: specify that the standard exceptions (std::exception) and your own base exception will be thrown. But when applied to a whole project, it simply means every single function will have this same specification: noise.
    – rwong
    Oct 28, 2016 at 20:06
  • I would point out that when it comes to exceptions, C++ and Java / C# are different languages. Firstly C++ isn't type-safe in the sense of allowing arbitrary code execution or data overwrite, secondly C++ doesn't print a nice stack trace. These differences forced C++ programmers to make different choices in terms of error and exception handling. It also means that certain projects could legitimately claim that C++ isn't a suitable language for them. (For example, I personally consider that TIFF image decoding shall not be permitted to be implemented in C or C++.)
    – rwong
    Oct 28, 2016 at 20:10
  • 1
    @rwong: A big problem with exception handling in languages which follow the C++ model is that catch is based upon the type of the exception object, when in many cases what matters is not so much the direct cause of the exception but rather what it implies about the system state. Unfortunately, the type of an exception generally says nothing about whether it caused code to disruptively exit a piece of code in a way that left an object in a partially-updated (and thus invalid) state.
    – supercat
    Oct 28, 2016 at 21:41
4

Consider a developer calling the C() function. He doesn't check the documentation so doesn't catch any exceptions. The call is unsafe

It's totally safe. He doesn't need to catch exceptions in every place, he can just throw a try/catch in a place where he can actually do something useful about it. It's only unsafe if he allows it to leak out of a thread, but it's typically not hard to prevent that from happening.

9
  • It is unsafe since he doesn't expect an exception coming out of C(), and consequently does not safeguard against the code following the call being skipped. If that code is required to guarantee that some invariance remains true, that guarantee goes poof at the first exception coming out of C(). There is no such thing as perfectly exception-safe software, and most certainly not where exceptions were never expected. Oct 31, 2016 at 11:29
  • 1
    @cmaster Him not expecting an exception coming out of C() is a big mistake. The default in C++ is that any function can throw - it requires an explicit noexcept(true) to tell the compiler otherwise. In absence of this promise, a programmer must assume that a function can throw.
    – Sjoerd
    Nov 3, 2016 at 13:40
  • 1
    @cmaster That is his own dumb fault and nothing to do with the author of C(). Exception safety is a thing, he should learn about it, and follow it. If he calls a function he doesn't know is noexcept, he should damn well handle what happens if it throws. If he needs noexcept, he should find an implementation which is.
    – DeadMG
    Nov 4, 2016 at 23:50
  • @Sjoerd and DeadMG: While the standard allows any function to throw an exception, that does not mean that projects must allow exceptions in their code. If you ban exceptions from your code, just like the OP's team-lead seems to be doing, you don't have to do exception-safe programming. And if you try to convince a team-lead to allow exceptions into a code base that was previously exception free, there will be a ton of C() functions that break in the presence of exceptions. This is really a very unwise thing to do, it is doomed to lead to a ton of problems. Nov 5, 2016 at 13:20
  • @cmaster This is about a new project - see the first comment on the accepted answer. So there are no existing functions that will break, as there are no existing functions.
    – Sjoerd
    Nov 5, 2016 at 16:05
3

If you are building a critical system, consider following your team lead's advice and not use exceptions. This is AV Rule 208 in Lockheed Martin's Joint Strike Fighter Air Vehicle C++ Coding Standards. On the other hand, MISRA C++ guidelines have very specific rules regarding when exceptions can and cannot be used if you are building a MISRA-compliant software system.

If you are building critical systems, chances are you are also running static analysis tools. Many static analysis tools will warn if you do not check the return value of a method, making cases of missing error handling readily apparent. To the best of my knowledge, similar tool support for detecting proper exception handling is not as strong.

Ultimately, I would argue that design by contract and defensive programming, coupled with static analysis is safer for critical software systems than exceptions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.