Do You (Really) Write Exception Safe Code

Do you (really) write exception safe code?

Your question makes an assertion, that "Writing exception-safe code is very hard". I will answer your questions first, and then, answer the hidden question behind them.

Answering questions

Do you really write exception safe code?

Of course, I do.

This is the reason Java lost a lot of its appeal to me as a C++ programmer (lack of RAII semantics), but I am digressing: This is a C++ question.

It is, in fact, necessary when you need to work with STL or Boost code. For example, C++ threads (boost::thread or std::thread) will throw an exception to exit gracefully.

Are you sure your last "production ready" code is exception safe?

Can you even be sure, that it is?

Writing exception-safe code is like writing bug-free code.

You can't be 100% sure your code is exception safe. But then, you strive for it, using well-known patterns, and avoiding well-known anti-patterns.

Do you know and/or actually use alternatives that work?

There are no viable alternatives in C++ (i.e. you'll need to revert back to C and avoid C++ libraries, as well as external surprises like Windows SEH).

Writing exception safe code

To write exception safe code, you must know first what level of exception safety each instruction you write is.

For example, a new can throw an exception, but assigning a built-in (e.g. an int, or a pointer) won't fail. A swap will never fail (don't ever write a throwing swap), a std::list::push_back can throw...

Exception guarantee

The first thing to understand is that you must be able to evaluate the exception guarantee offered by all of your functions:

  1. none: Your code should never offer that. This code will leak everything, and break down at the very first exception thrown.
  2. basic: This is the guarantee you must at the very least offer, that is, if an exception is thrown, no resources are leaked, and all objects are still whole
  3. strong: The processing will either succeed, or throw an exception, but if it throws, then the data will be in the same state as if the processing had not started at all (this gives a transactional power to C++)
  4. nothrow/nofail: The processing will succeed.

Example of code

The following code seems like correct C++, but in truth, offers the "none" guarantee, and thus, it is not correct:

void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
X * x = new X() ; // 2. basic : can throw with new and X constructor
t.list.push_back(x) ; // 3. strong : can throw
x->doSomethingThatCanThrow() ; // 4. basic : can throw
}

I write all my code with this kind of analysis in mind.

The lowest guarantee offered is basic, but then, the ordering of each instruction makes the whole function "none", because if 3. throws, x will leak.

The first thing to do would be to make the function "basic", that is putting x in a smart pointer until it is safely owned by the list:

void doSomething(T & t)
{
if(std::numeric_limits<int>::max() > t.integer) // 1. nothrow/nofail
t.integer += 1 ; // 1'. nothrow/nofail
std::auto_ptr<X> x(new X()) ; // 2. basic : can throw with new and X constructor
X * px = x.get() ; // 2'. nothrow/nofail
t.list.push_back(px) ; // 3. strong : can throw
x.release() ; // 3'. nothrow/nofail
px->doSomethingThatCanThrow() ; // 4. basic : can throw
}

Now, our code offers a "basic" guarantee. Nothing will leak, and all objects will be in a correct state. But we could offer more, that is, the strong guarantee. This is where it can become costly, and this is why not all C++ code is strong. Let's try it:

void doSomething(T & t)
{
// we create "x"
std::auto_ptr<X> x(new X()) ; // 1. basic : can throw with new and X constructor
X * px = x.get() ; // 2. nothrow/nofail
px->doSomethingThatCanThrow() ; // 3. basic : can throw

// we copy the original container to avoid changing it
T t2(t) ; // 4. strong : can throw with T copy-constructor

// we put "x" in the copied container
t2.list.push_back(px) ; // 5. strong : can throw
x.release() ; // 6. nothrow/nofail
if(std::numeric_limits<int>::max() > t2.integer) // 7. nothrow/nofail
t2.integer += 1 ; // 7'. nothrow/nofail

// we swap both containers
t.swap(t2) ; // 8. nothrow/nofail
}

We re-ordered the operations, first creating and setting X to its right value. If any operation fails, then t is not modified, so, operation 1 to 3 can be considered "strong": If something throws, t is not modified, and X will not leak because it's owned by the smart pointer.

Then, we create a copy t2 of t, and work on this copy from operation 4 to 7. If something throws, t2 is modified, but then, t is still the original. We still offer the strong guarantee.

Then, we swap t and t2. Swap operations should be nothrow in C++, so let's hope the swap you wrote for T is nothrow (if it isn't, rewrite it so it is nothrow).

So, if we reach the end of the function, everything succeeded (No need of a return type) and t has its excepted value. If it fails, then t has still its original value.

Now, offering the strong guarantee could be quite costly, so don't strive to offer the strong guarantee to all your code, but if you can do it without a cost (and C++ inlining and other optimization could make all the code above costless), then do it. The function user will thank you for it.

Conclusion

It takes some habit to write exception-safe code. You'll need to evaluate the guarantee offered by each instruction you'll use, and then, you'll need to evaluate the guarantee offered by a list of instructions.

Of course, the C++ compiler won't back up the guarantee (in my code, I offer the guarantee as a @warning doxygen tag), which is kinda sad, but it should not stop you from trying to write exception-safe code.

Normal failure vs. bug

How can a programmer guarantee that a no-fail function will always succeed? After all, the function could have a bug.

This is true. The exception guarantees are supposed to be offered by bug-free code. But then, in any language, calling a function supposes the function is bug-free. No sane code protects itself against the possibility of it having a bug. Write code the best you can, and then, offer the guarantee with the supposition it is bug-free. And if there is a bug, correct it.

Exceptions are for exceptional processing failure, not for code bugs.

Last words

Now, the question is "Is this worth it ?".

Of course, it is. Having a "nothrow/no-fail" function knowing that the function won't fail is a great boon. The same can be said for a "strong" function, which enables you to write code with transactional semantics, like databases, with commit/rollback features, the commit being the normal execution of the code, throwing exceptions being the rollback.

Then, the "basic" is the very least guarantee you should offer. C++ is a very strong language there, with its scopes, enabling you to avoid any resource leaks (something a garbage collector would find it difficult to offer for the database, connection or file handles).

So, as far as I see it, it is worth it.

Edit 2010-01-29: About non-throwing swap

nobar made a comment that I believe, is quite relevant, because it is part of "how do you write exception safe code":

  • [me] A swap will never fail (don't even write a throwing swap)
  • [nobar] This is a good recommendation for custom-written swap() functions. It should be noted, however, that std::swap() can fail based on the operations that it uses internally

the default std::swap will make copies and assignments, which, for some objects, can throw. Thus, the default swap could throw, either used for your classes or even for STL classes. As far as the C++ standard is concerned, the swap operation for vector, deque, and list won't throw, whereas it could for map if the comparison functor can throw on copy construction (See The C++ Programming Language, Special Edition, appendix E, E.4.3.Swap).

Looking at Visual C++ 2008 implementation of the vector's swap, the vector's swap won't throw if the two vectors have the same allocator (i.e., the normal case), but will make copies if they have different allocators. And thus, I assume it could throw in this last case.

So, the original text still holds: Don't ever write a throwing swap, but nobar's comment must be remembered: Be sure the objects you're swapping have a non-throwing swap.

Edit 2011-11-06: Interesting article

Dave Abrahams, who gave us the basic/strong/nothrow guarantees, described in an article his experience about making the STL exception safe:

http://www.boost.org/community/exception_safety.html

Look at the 7th point (Automated testing for exception-safety), where he relies on automated unit testing to make sure every case is tested. I guess this part is an excellent answer to the question author's "Can you even be sure, that it is?".

Edit 2013-05-31: Comment from dionadar

t.integer += 1; is without the guarantee that overflow will not happen NOT exception safe, and in fact may technically invoke UB! (Signed overflow is UB: C++11 5/4 "If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.") Note that unsigned integer do not overflow, but do their computations in an equivalence class modulo 2^#bits.

Dionadar is referring to the following line, which indeed has undefined behaviour.

   t.integer += 1 ;                 // 1. nothrow/nofail

The solution here is to verify if the integer is already at its max value (using std::numeric_limits<T>::max()) before doing the addition.

My error would go in the "Normal failure vs. bug" section, that is, a bug.
It doesn't invalidate the reasoning, and it does not mean the exception-safe code is useless because impossible to attain.
You can't protect yourself against the computer switching off, or compiler bugs, or even your bugs, or other errors. You can't attain perfection, but you can try to get as near as possible.

I corrected the code with Dionadar's comment in mind.

Which kind of code is considered exception safe?

"Exception safe" is a fairly overloaded term, but I would use it to describe sections of code which can have exceptions thrown through them and still maintain certain invariants (such as - nothing changes, no resources are leaked, all objects keep a valid state).

As an example, your void * printHello (void* threadId) function is correct (in that it always matches pthread_mutex_lock (&demoMutex) with pthread_mutex_unlock (&demoMutex)), but if someone changed the section in the middle so that it could throw an exception (for example, by setting the throw flags on std::cout), then your code would then permanently lock demoMutex, with no hope for it ever being released. This would constitute a bug in your program. void * printHello (void* threadId) is said to be "exception unsafe" because of this way in which bugs can easily be introduced into your program by adding exception handling to seemingly unrelated parts.

Using a RAII class to manage resources a good way to go about writing exception safe code (and is the resource management style to use in C++), because it avoids the need for duplicate manual cleanup in catch(...) blocks, and it enforces the cleanup of resources by using the type system.

Is this C++ code exception safe?

Is the above code sample exception safe?

H**l no!

It's exactly like you're saying. The code you show leaks like a sieve upon exceptions.

RAII or std::unique pointers should be used.

Even for pre-standard c++ code, there was the good ole std::auto_ptr.


But since this is an official code sample, I am not sure about this.

Well, Open Source doesn't necessarily mean it's anything "official", or anyhow "better" than closed source code, where the developers are hidden from the customer's eyes.

That's all.


Well, to be fair (with the MySQL contributors):

It is a minimal usage example, and any memory leaks would be cleaned up when the main() function block exits and the process dies.

But not really a gem of a good example. Given the fact that many mediocre programmers are out there, and make up their living with "CTRL-C CTRL-V codez from teh samplez and tuz" I'm not so sure that's a good strategy.


Conclusio:

Don't blindly copy and take over example code, even not that one from the official documentation pages of a library, tool, or from a book verbatim.

These are meant to be simplified, and may focus on the usage of the library features with least distraction of the reader.

They're fairly leaving you being in charge to use their stuff correctly.


What I would do to fix it:

I'd consider to provide a thin wrapping layer based on owned and shared pointers as simple class members (see Dynamic memory management) and lambda functions:

class MySQLQuery;

class MySQLConnection {
std::function<void(void*)> driverCleanup_;
std::function<void>(void*) connectionCleanup_;
std::unique_ptr<sql::Driver> driver_;
std::shared_ptr<sql::Connection> con_;
public:
MySQLConnection
( const std::string& ipAddress
, const std::string& user
, const std::string& password
) :
driverCleanup_([](void *driver) {
// (sql::Driver*)driver->cleanup(); ## Whatever necessary
delete driver;
}) ,
connectionCleanup_([](void *connection) {
// (sql::Connection*)connection->cleanup();
delete connection;
}),
driver_(get_driver_instance(),driverCleanup_),
con_(std::make_shared(driver_->connect(ipAddress,user,password)
,connectionCleanup_))
{}

std::unique_ptr<MySQLQuery> CreateQuery(const std::string& query);
};

class MySQLQuery {
friend class MySQLConnection;

std::shared_ptr<sql::Connection> con_;
std::unique_ptr<sql::Statement stmt_;
MySQLQuery
( std::shared_ptr<sql::Connection> connection
, const std::string& query
) : con_(connection)
, stmt_(con_->createStatement()
{
}
public:
// And so on ...
MySQLResultSet Execute() {
return stmt_->executeQuery("SELECT 'Hello World!' AS _message");
}
};

std::unique_ptr<MySQLQuery> MySQLConnection::CreateQuery(const std::string& query) 
{
return std::make_unique<MySQLQuery>(con_,query);
}

Also, you might consider to catch any exceptions in the deleter functions code. Though *never (re-)throw exceptions from deleters. This screws you up all of a kind.

Being harnished in all kind of directions if the cost is low, is always a good idea IMO.

Is it worth making my code less readable for providing exception safety in case of out of memory errors?

The answer to your question depends on whether you can and want to handle memory allocation errors, other than by crashing. If you do, you will have to implement measures beyond just catching std::bad_alloc. Most modern operating systems implement memory overcommit, which means memory allocation will succeed, but accessing the allocated memory for the first time will cause a page fault, which will normally result in a crash. You have to explicitly fault allocated pages in your memory allocator to detect out of memory condition before returning the pointer to the caller.

Regarding your code modification, you don't have to modify your call to push_back:

this->inventory.push_back(std::move(item));

If push_back (or any other potentially reallocating method) needs to allocate a new buffer, it will do this before moving the new item into the vector. Obviously, this is because there is no room in the vector to move the new element to. And when the buffer is reallocated and all existing elements are moved/copied to the new buffer, the new element is moved as the final step.

In other words, if push_back throws, you can be sure the new item has not been moved. If it doesn't throw and returns normally, the item is moved from.

How do you keep track of exception safety guarantees offered by each function

I don't think you need to do anything special.

The only ones I really document are no-throw and that is because the syntax of the language allows it.

There should be no code in your project that provides no guarantee. So that only leaves strong/basic to document. For these I don't think you need to explicitly call it out as it not really about the methods themselves but the class as a whole (for these two guarantees). The guarantees they provide are really dependent on usage.

I would like to think I provide the strong guarantee on everything (but I don't) sometimes it is too expensive sometimes its just not worth the effort (if things are going to throw they will be destroyed anyway).



Related Topics



Leave a reply



Submit