Top 15 C++ Exception handling mistakes and how to avoid them.
Do you use exception handling in your C++ code?
If you don’t, why not?
Perhaps you’ve been conditioned to believe that exception handling is bad practice in C++. Or maybe you think that it’s prohibitively expensive in terms of performance. Or maybe it’s just not the way your legacy code is laid out and you’re stuck in the rut.
Whatever your reason is, it’s probably worth noting that using C++ Exceptions instead of error codes has a lot of advantages. So unless you’re coding some real-time or embedded systems, C++ exceptions can make your code more robust, maintainable and performant in the normal code path (yes performant, you read that right !).
In this article we’re going to look at 15 mistakes that a lot of developers make when just stating off with C++ exceptions or considering using C++ exceptions.
Mistake # 1: Dismissing Exception Handling as expensive in favor of using error codes
If you’re coming from C or COM programming, using exceptions might feel a bit unnatural. One of the most misleading things new C++ developers hear is that exceptions are super expensive and should not be used because they’ll tank your performance. This is an old wive’s tale.
The main model used for C++ Exceptions now-a-days in VC++ 64 bit and GNU C++ compiler is the Zero-Cost Model. In this model, the compiler generates static lookup tables that are used to determine the handler for a thrown exception. Essentially, this means that you don’t pay any cost associated with exception handling unless an exception occurs. The exact cost of the situation when an exception does occur is very specific to the system under test, but a few C++ experts predicts that this is not prohibitively high.
“But exceptions are expensive!” Not really. Modern C++ implementations reduce the overhead of using exceptions to a few percent (say, 3%) and that’s compared to no error handling. Writing code with error-return codes and tests is not free either. As a rule of thumb, exception handling is extremely cheap when you don’t throw an exception. It costs nothing on some implementations. All the cost is incurred when you throw an exception: that is, “normal code” is faster than code using error-return codes and tests. You incur cost only when you have an error.” – isoccp C++ FAQ
A second objection against exceptions is that it causes code size bloat. Scott Meyer in “More Effective C++” notes that using exceptions could lead to 5-10% increase in executable size. Bruce Eckel estimates the same numbers to be between 5 and 15 percent. While this can represent an issue for embedded systems, for regular desktop or service application programming, this is usually not a problem. And you also need to think that if you’re not using exceptions, you’ll need a whole bunch of error handling code intertwined with your core logic, which will also increase the size of the binary.
So here’s the bottom-line, don’t just dismiss using C++ exceptions because “someone” said it’s expensive. MEASURE for yourself and then make a call. Think about what you’re losing out by not using exceptions. If you hit a performance / size bottleneck, think about scaling out rather than scaling up or trying to squeeze each drop of perf by making the code obscure and unmaintainable.
Mistake # 2: Not understanding the stack unwinding process
Beyond knowing how to use the try/catch syntax, one of the fundamental concepts to know regarding C++ exception handling is the concept of Stack Unwinding.
When an exception is thrown and control passes from a try block to a handler, the C++ run time calls destructors for all automatic objects constructed since the beginning of the try block. This process is called stack unwinding. The automatic objects are destroyed in reverse order of their construction. If an exception is thrown during construction of an object consisting of sub-objects or array elements, destructors are only called for those sub-objects or array elements successfully constructed before the exception was thrown.
Why should you know this ? Because this'll help you understand the exception handling tips and tricks for making your code robust and efficient. A full discussion of the Stack Unwinding process is beyond the scope of this article – but here is an excellent reference from msdn : https://msdn.microsoft.com/en-us/library/hh254939.aspx.
Mistake # 3: Using exceptions for normal code-flow
An exception should be thrown when a situation has come up that prevents the called function from fulfilling its work. If the function can recover from the problem so that it can still provide its user with the services it promised, then it has handled the problem and should not throw an exception.
Consider the following example:
Imagine that you have a API that tries to establish a network connection to a specific service endpoint. If the API encounters an error it cannot recover from, it should throw an exception back to the client code calling the API. The client code can then catch the exception and decide if it wants to retry the connection after waiting for a specific time or if it wants to try a different network endpoint. Notice that the API, whose sole objective is to establish the connection has no recourse but to throw the exception when it fails to fulfill it’s promise to establish a connection. Now if the API promised to retry the connection with exponential back-off, the right way would be to not throw an exception untill all the retries has been exhausted.
Moreover, Exceptions should be reserved for situations which are truly exceptional. In practice, if your code is encountering exceptions more than 1% of the time, then exception handling mechanism isn’t the right choice to deal with it and the design should be re-visited.
Note that if your try block is hit approximately with the same frequency as your catch block, then it’s very clear indication that exception-handling mechanism is overused/abused in your program and the design needs to be re-visited / root caused for large number of exceptions identified.
Mistake # 4: Not using exceptions in constructors when object creation fails
Some developers operate under the notion that throwing exceptions from a constructor is a sacrilege. This is not correct.
When a constructor fails and it does not throw an exception, it leaves the object in a non-functional “zombie” state. Since a constructor cannot return a value, it can only indicate failure in an indirect way by setting a state variable inside the object indicating that the object was not properly initialized. If going down this route, you’ll need to provide accessor functions to get the state and the client could still forget to check the object state before using the object. This can lead to some very arcane downstream failures (think thousands of lines away from where the zombie object was created).
Exceptions in the constructor avoids this problem and lets the program “Fail Fast” such that if there is a recourse (like instantiating an auxiliary object), the client code can take that corrective step.
Mistake # 5: Throwing exceptions in destructors or in overloaded delete or delete[] operator
If a destructor is invoked during the stack-unwinding process triggered by the throwing of an exception, and the destructor itself throws an exception, terminate() is invoked and your program dies !
The same applies for any routines the destructor can call. If the routines called by the destructor throws an exception, the destructor should catch them, log a message and then terminate the program if deemed necessary. But the destructor should NOT re-throw the exception.
The same thing applied if you’ve overloaded the delete() and delete[]() operator – these must not throw exceptions!
For an in-depth discussion please refer to item #8 in Exceptional C++.
Mistake # 6: Not Throwing an exception by value
If you throw a pointer to an exception object, you’ll need to deal with memory management issues. You cannot throw a pointer to a stack allocated object because the stack will be un-wound before the pointer reaches the call site. You can of course throw a pointer to dynamically allocated memory. But if you do that, there are two issues – the exception you’re trying to throw could be caused because you ran out of memory- so trying to allocate new heap memory for the exception object might not work ! Even if it does, now your catching code is responsible for deallocating the memory.
Throwing by value ensures that the compiler takes care of managing the memory for the exception object. All you need to ensure is that you implement a non-throwing copy constructor of your exception class.
Mistake # 7: Not catching an exception by reference or consts reference
If you catch an exception object by pointer, you have no idea whether you should delete the object or not.
If you catch an object by value, you get rid of the delete or not to delete conundrum , but now you’re copying the exception object twice ! Also, catching by value can result in object slicing problem, whereby derived class exception objects caught as base class exceptions have their derived class specific data sliced off .
Catching by reference (and const reference where possible) gets rid of the memory management issues, the unnecessary copying of exception object and the object slicing problem.
Always throw an exception by value and catch by reference or const reference if possible.
Mistake # 8: Using Exception specifications in code
Exception specifications affects a function’s type inconsistently in different situations. For example, they are illegal in typedefs for a function pointer but is legal in similar code without the typedef.
From a performance standpoint, exception specifications cause the compiler to inject additional run-time overhead in the form of implicit try/catch blocks around function body to enforce via run-time checking that the function only emits the listed exceptions. They might also prevent certain compiler optimizations – for example, some compilers may not be able to inline code which has exception specifications defined.
Because of the above mentioned issues, exception specifications were never widely used and has been deprecated in the current C++ standard.
For a thorough discussion on the topic, please refer to the following article by C++ Guru Herb Sutter : http://www.gotw.ca/publications/mill22.htm
Mistake # 9: Not realizing the implications of "noexcept" specification
Under the C++ 11 standard, a function can specify that it does not throw exceptions by providing a noexcept specification. For example:
void ProcessRequest() noexcept; // won't throw
void FillData() ; // might throw
However, it is critical to understand that the compiler does not check the noexcept specification at compile time. However, if a function that is marked with noexcept does end up throwing, terminate() is called on the program.
The key point here is to understand that you should not mark a function as noexcept unless you're absolutely sure that the function or any other function it calls WILL NOT throw an exception – otherwise your program will terminate abruptly.
Mistake # 10: Mixing Exceptions and Error codes
If you're dealing with legacy software, you might find code that mixes error codes and exceptions. What am I talking about ? Consider the piece of code below:
#include "stdafx.h" #include <iostream> #include <iostream> #include <exception> using namespace std; enum AircraftError { WingsOnFire = 1, WingBroken = 2, NoRunway = 3, Crahed = 4 }; class AircraftException : public exception { public: AircraftException(const char* errMessage, AircraftError errorType) :m_ErrMessage(errMessage), m_ErrorType(errorType){} // overriden what() method from exception class const char* what() const noexcept{ return m_ErrMessage; } AircraftError GetError() { return m_ErrorType; } private: const char* m_ErrMessage; AircraftError m_ErrorType; }; int main() { try { throw AircraftException("crashed", AircraftError::Crahed); } catch (AircraftException& e) { cout << e.what() << '\n'; if (e.GetError() == AircraftError::WingsOnFire) { // Fire extinguishers } else if (e.GetError() == AircraftError::WingBroken) { // Cannot do anything in flight - pray and rethrow } else if(e.GetError()== AircraftError::NoRunway) { //Call Air Traffic control to clear up runway } else { // We have crashed - throw throw; } } return 0; }
The above code conveniently mixes the error code handling pattern of C with exceptions in C++. This is totally unnecessary and unnecessarily complicates the catch block.
Further, the general rule for exception handling is that you should only catch exceptions that you can handle or plan to transform in a certain way. In the above example, if we detect WingBroken or Crashed exception, we just re-throw hoping someone up the chain is available to catch and handle the exception. This catching and re-throwing of exceptions we cannot handle is totally unnecessary and comes with a performance penalty.
The ideal way would have been to take the errors defined in the enum and translate them into separate exceptions and catch the specific ones we can handle , while allowing the others to bubble up the call chain.
Mistake # 11: Not Deriving Custom Exception classes from from a common base class, std::exception or one of it’s subclasses
This is particularly important for API design. If your client code misses handling code for one of the exceptions your API might throw, it can lead to abnormal program behavior. However, if they’re catching a std::exception anyways (and they should be), they’ll be able to at-least catch and log the exception thrown from your API.
Mistake # 12: Throwing exception in an exception class constructor
This might confuse the runtime which is already dealing with one exception during the stack unwinding process. In most situations, this’ll raise an unhandled exception leading to program termination.
DO NOT throw exceptions in an exception class constructor. Exception class constructors MUST not fail.
Mistake # 13: Not understanding the difference between throw and throw e from a catch block
When you re-throw an exception using throw, the original exception object is preserved along with any modifications that has been applied to it.
When you re-throw using throw e, a copy of the original exception is created which incurs a perf hit. Also, if you’re catching exceptions of a derived class via a base class in catch clause, re-throwing a new exception via throw e will make the newly thrown exception lose derived-class-specific data during the copy-construction of a base class instance.
Morale of the story – in most cases re-throwing the original exception via throw is preferable than creating a new exception and throwing that.
The following stack overflow link has a good description of the topic in more detail.
http://stackoverflow.com/questions/2360597/c-exceptions-questions-on-rethrow-of-original-exception
Mistake # 14: Using setjmp and longjmp in c++ code for exception handling
These two constructs have no business being used in modern C++ code unless you’re in some specialized situation dealing with embedded systems or interfacing with some legacy libraries using those routines.
From msdn:
"Do not use setjmp and longjmp in C++ programs; these functions do not support C++ object semantics. Also, using these functions in C++ programs may degrade performance by preventing optimization on local variables. Use the C++ exception handling try/catch constructs instead."
Mistake # 15: Swallowing Exceptions
Swallowing critical exceptions will cause your program to do either of two things – to fail in unexpected ways downstream or prevent the program from fulfilling it’s purpose. Sometimes programmers will catch any exception via catch(…) and then swallow them . This is usually done for exceptions that the programmer did not foresee happening. However, this can lead to downstream failure – sometimes with no obvious reason for the failure since the stacktrace disappears with the swallowed exception.
If you must swallow exceptions, make sure that you Log the exception as well as document them in code and in your documentation.
Where do we go from here?
Exception handling is a very complex topic and this article only scratches the surface. You can continue your explorations via the following books and resources in the given order ( basic to advanced)
- C++ Exception FAQ on isocpp.org
- More Effective C++ – 35 new ways to improve your programs and designs – items 9 through 15.
- C++ Coding Standards – 101 Rules, Guidelines and Best Practices – items 68 through 75.
- Exceptional C++ – 47 Engineering puzzles, programming problems and solutions – items 8 through 19
Good luck in your exploration of C++ exceptions. Please share if you liked the article 🙂