RAII versus Exceptions

Recently I received a question on Twitter whether to prefer RAII over Exceptions. I have seen similar questions being asked again and again over time, so there seems to be some need for clarification. 

Use both RAII AND exceptions

RAII and exceptions do not contradict each other. On the contrary, they can be considered two sides of the same medal. They complement each other, although RAII has its uses even in exception free contexts.

The life of an exception

The life time of an exception can be divided into three stages and locations in code:

  1. The exception is thrown. This is the point where some exceptional error occurs. In code this is simply the `throw` statement.
  2. The exception is active. While the exception is active, the stack is unrolled. That means, functions are aborted early and destructors of existing stack objects in those functions are called. This continues back to the last function call that has happened inside a `try` block.
  3. The exception is caught. When a function called from a try block is unrolled due to an exception, then the try block is aborted likewise, including destruction of stack objects. After that the first catch handler that matches the exception type is executed. If there is no such catch handler, the function containing the try block is aborted. The exception remains active until a try block with a matching catch handler is found.
  4. The exception is translated. Yes, this is stage four of three. It actually is stage three, the catching of an exception, followed by stage one, i.e. throwing a new exception.

Throwing

Exceptional behavior means something that can not be handled right away. It also means it is not part of the normal program flow. To decide what is and is not exceptional can be a bit of a problem. There are problems or error conditions that may or may not be exceptional depending on your application domain.

For example, having malformed user input is not exceptional. On the contrary, it is to be expected. That means we should check any user input for validity instead of just assuming that a file named `grtzlph.jpg` actually exists. Or that someone indeed has `Robert’) DROP TABLE Students;–` written on their passport.

Active exception

Having an active exception is actually the most interesting part of the exception lifetime. The stack unrolling means that the only way we can interfere is by having destructors that actually do something.

This is where RAII comes in: RAII classes are the only sane way to clean up anything that needs cleaning up. This includes releasing memory and other resources like file handles and database connections. It also means resetting stuff back to how it was, like the mouse cursor, colors, fonts or what ever we have changed temporarily.

Catching

When we catch and actually handle an exception, the hard part is over. The stack has been unrolled, and the RAII objects have done their duty. Anything that has not been cleaned up probably won’t be cleaned up, ever.

There may be cases where we not actually handle the exception but instead transform it to some other means of error reporting, e.g. by returning an error value. In that case we enter the lands where RAII does not help with exceptions, because there simply are no exceptions. (You really don’t want to have sections of code where more than one error reporting mechanism is used. Trust me.)

Translation

When we catch and not really handle the exception but throw the same or another exception, we translate it. The new exception thrown typically is either of another type, or we add some valuable information to the exception object before we rethrow it.

Catching for cleanup

If you have a look at legacy code, you often find places where a catch handler neither handles the error nor translates the exception. Instead, some cleanup is performed, and the the exception is rethrown.

That means, there are try/catch sections in the code that do not fit any of the phases I described above. They are neither handling the exception nor adding or transforming exception information.

These are the occasions where we have a lack of RAII objects. It is often relatively simple to introduce small classes that do the cleanup in their destructor. There even are tricks, usually with macros and/or templates, that let you create such a scope guard object on the fly.

Maybe the “prefer RAII over Exceptions” question actually is about preferring RAII over these kind of catch-cleanup-rethrow operations. In that case my answer is definitely “prefer RAII over manual exception fiddling”.

You may also have catch handlers that do more than one thing, i.e. cleanup and exception handling or translation. You still should factor out the cleanup to RAII classes, to give that catch handler a single purpose instead of two or more.

RAII has more uses

RAII certainly shines best in the (possible) presence of exceptions. However, this does not mean that RAII only is a good idea if you have code that may throw.

On the contrary, the automatic cleanup performed by RAII objects comes in handy whenever there is something to be cleaned up or put back into its original state. For example it allows us to use multiple return statements without having to keep track about what needs to be cleaned up and what not.

It also allows us to maintain the two related operations that acquire and release a responsibility close together instead of before and after the work we do while we have that responsibility.

Conclusion

If there is a question “should I use RAII?”, then there usually is something to be cleaned up. And if there is something to be cleaned up, then the answer will be “yes, use RAII”. Not instead of exceptions, but regardless of whether there are exceptions or not.

If you do so, then there is no “RAII vs. exceptions”. Code related to exceptions is where errors occur and are handled, while RAII is everywhere else, making sure that exceptions don’t have nasty side effects.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

Leave a Reply

Your email address will not be published. Required fields are marked *