If learning exception safety may be likened to a journey, then Alan Griffiths is an excellent tour guide. He shows us several alternate routes, and he always keeps the trip interesting.
The use of animals in maps was commonplace from the earliest times. Mans deepest fears and superstitions concerning wide expanses of uncharted seas and vast tracts of terra incognita were reflected in the monstrous animals that have appeared on maps ever since the Mappa Mundi. Roderick Barron in Decorative Maps
For many developers, C++ exception handling is like this a Dark Continent with poor maps and rumors of ferocious beasts. It neednt be this way; if youll come with me, Ill be your guide to the landmarks and fauna of this region.
In order to discuss exception safety, we need to cover a lot of territory. The next section identifies the exception safe mountains in the distance. Well be using them as landmarks on our travels if you dont take the time to get your bearings now, youll end up in the wastelands.
Once youve learned the landmarks, Ill show you a well-trodden path that leads straight towards the highest peak and straight through a tar pit. From experience, Ive concluded that everyone has to go down this way once, so Ill go with you to make sure you come back. Not everyone comes back; some give up on the journey, and others press on deeper and deeper into the tar pit until they sink from sight.
During our journey, Ill tell you the history of how the experts searched for a long time before they discovered a route that bypasses that tar pit and other obstacles. Most maps dont show this route yet, but Ill show you the signs to look out for. Ill also show you that the beasts are friendly and how to enlist their aid.
Now look into the distance and youll see two peaks these are heights of exception safety and are our final destination.
The Mountains (Landmarks of Exception Safety)
The difficulty in writing exception-safe code isnt in writing the code that throws an exception, or in writing the code that catches the exception to handle it. There are many sources that cover these basics. Im going to address the greater challenge of writing the code that lies in between the two.
Imagine for a moment the call stack of a running program: function a has called function b, b has called c, and so on, until we reach x. Function x encounters a problem and throws an exception. This exception causes the stack to unwind, deleting automatic variables along the way, until the exception is caught and dealt with by a.
Im not going to spend any time on how to write functions a or x. Im sure that the author of x has a perfectly good reason for throwing an exception (running out of memory, disc storage, or whatever) and that the author of a knows just what to do about it (display: Sorry, please upgrade your computer and try again!).
The difficult problem lies in writing all the intervening functions in a way that ensures that something sensible happens as a result of this process. If we can achieve this, we have exception-safe code. Of course, that begs the question, what is something sensible? To answer this, let us consider a typical function f in the middle of the call stack. How should f behave?
Well, if f were to deal with the exception, it might be reasonable for it to complete its task by another method (a different algorithm, or returning a failed status code). However, we are assuming the exception wont be fully handled until we reach a. (It may be caught and rethrown possibly after being translated to a different exception.) Since f doesnt run to completion, the least we might reasonably expect is the basic exception safety guarantee:
- f doesnt complete its task; but
- if f has opened a file, acquired a lock on a mutex, or otherwise allocated a resource, then the resource should not leak. (The file must be closed, the mutex must be unlocked, etc.); and
- if f changes a data structure, then that structure should remain useable e.g., no dangling pointers.
These conditions the basic exception safety guarantee identify the first and smaller of our landmark mountains. Take a good look at it so that youll recognize it later.
The basic exception safety guarantee ensures that if f updates the system state, then the state must remain usable. Note that usable isnt quite the same as correct. For example, if f were the assignment operator of an address class, part of the address may have been changed, leaving a usable address object containing an incorrect address.
If you are new to exceptions, the basic exception safety guarantee may seem daunting. But not only will we reach this in our travels, we will be reaching an even higher peak called the strong exception safety guarantee. The strong exception safety guarantee places a more demanding constraint on f (one that implies all the others):
4. If f terminates by propagating an exception, then it has made no change to the state of the program.
Note that it is impossible to implement f to deliver either the basic or strong exception safety guarantees if the behavior in the presence of exceptions of the functions it calls isnt known, mainly because of possible side effects. This is particularly relevant when the client of f (that is e) supplies the functions to be called either as callbacks, as implementations of virtual member functions, or via template parameters. In such cases, the only recourse is to document the constraints on them as, for example, the standard library does for types supplied as template parameters to the containers.
If we assume a design with fully encapsulated data, then each function need only be held directly responsible for aspects of the object of which it is a member. For the rest, the code in each function must rely on the functions it calls to behave as documented. (We have to rely on documentation in this case, since in C++ there is no way to express these constraints in the code.)
As we proceed, well find frequent outcrops of the rock that supports our mountains, which is named after the area in which it is found and is called the no-throw exception safety guarantee as the name suggests, this implies that the corresponding function will never propagate an exception. Clearly operations on the fundamental types provide this guarantee, as will any sequence of no-throw operations. If it were not for the firm footing that these outcrops of the no-throw exception safety guarantee provide, we would have great difficulty ascending the heights.
Before we continue, well rest here a while, and Ill tell you a little of the history of this landscape. Remember our landmarks and the rock that comes from the area well soon be making use of them on our journey.
A History of This Territory
The C++ people first came to visit the land of exceptions around 1990 when Margaret Ellis and Bjarne Stroustrup published the Annotated Reference Manual [1]. Under the heading Experimental Features, this described the basic mechanisms of exceptions in the language. In this early bestiary, there is an early description of one of the friendly beasts we shall be meeting later on: it goes by the strange name of Resource-Acquisition-Is-Initialization.
By the time the ISO C++ Standards committee circulated Committee Draft 1 in early 1995, C++ people were truly living in exception land. They hadnt really mapped the territory or produced an accurate bestiary, but they were committed to staying and it was expected that maps and bestiaries would soon be available.
By late 1996 when Committee Draft 2 was circulated, however, the difficulties of this undertaking had become apparent. Around this time there came a number of reports from individual explorers. For example: Dave Abrahams identified the mountains we are using as landmarks in his paper Exception Safety in STLPort [2], although the basic exception safety guarantee was originally dubbed the weak exception safety guarantee.
Some other studies of the region were produced by H. Muller [3] and Herb Sutter [4, 5]. A little later came a sighting of another of the friendly beasts that we will meet soon, called Acquisition-Before-Release. This beast was first known by a subspecies named Copy-Before-Release and was identified by Kevlin Henney [6]; the subspecies is distinguished by the resources allocated being copies of dynamic objects.
By the time the ISO C++ Language Standard was published in 1998, the main tracks through the territory had been charted. In particular there are clauses in the Standard guaranteeing the behavior of the standard library functions in the presence of exceptions. Also, in a number of key places within the Standard, special mention is made of another friendly beast the Swap algorithm in its incarnation as the std::swap template function. We will be examining Swap after our detour through the tar pit.
Since the publication of the ISO Standard, more modern charts have been produced, such as by the author in an early version of this article [7]. Bjarne Stroustrup [8] and Dave Abrahams [9] follow a similar route in their examinations of exception safety in the standard library. Herb Sutter [10] takes a different route, but the same landmarks are clearly seen; this is also the first reference to the no-throw exception safety guarantee (although it is clear that earlier explorers knew and exploited it).
Okay, thats enough rest, we will now take the obvious path and head directly towards the strong exception safety guarantee.
The Tar Pit
It is time to consider an example function, and for this part of the journey, I have chosen the assignment operator for the following class:
class PartOne { /* omitted */ }; class PartTwo { /* omitted */ }; class Whole { public: // ...Lots omitted... Whole& operator=(const Whole& rhs); private: PartOne* p1; PartTwo* p2; };Those of you that have lived in the old country will know the classical form for the assignment operator. It looks something like the following:
Whole& Whole::operator=(const Whole& rhs) { if (&rhs != this) { delete p1; delete p2; p1 = new PartOne(*rhs.p1); p2 = new PartTwo(*rhs.p2); } return *this; }If youve not seen this before, dont worry because in the new land, it is not safe. Either of the new expressions could reasonably throw (since at the very least they attempt to allocate memory), and this would leave the p1 and p2 pointers dangling. In theory the delete expressions could also throw but in this article, we will assume that destructors and deallocation functions make the no-throw exception safety guarantee (see the sidebar, Destructors That Throw Exceptions).
The obvious solution to the problems caused by an exception being propagated is to catch the exception and do some cleanup before throwing it again. After doing the obvious we have:
Whole& Whole::operator=(const Whole& rhs) { if (&rhs != this) { PartOne* t1 = new PartOne(*rhs.p1); try { PartTwo* t2 = new PartTwo(*rhs.p2); delete p1; delete p2; p1 = t1; p2 = t2; } catch (...) { delete t1; throw; } } return *this; }Lets examine why this works:
- An exception from the first new expression isnt a problem we havent yet allocated any resources or modified anything, and the new expression provides the strong guarantee.
- If an exception is propagated from the second new expression, we need to release t1. So we catch the exception, delete t1, and rethrow the exception to let it propagate.
- Because we are assuming that destructors and operator delete dont throw, we can pass over the two delete expressions without incident. Similarly the two assignments are of built-in types (pointers), so they cannot throw an exception.
- The state of Whole isnt altered until weve done all the things that might throw an exception.
If you peer carefully through the undergrowth, you can see the first of the friendly beasts. This one is called Acquisition-Before-Release. It is recognized because the code is organized so that new resources (the new PartOne and PartTwo) are successfully acquired before the old ones are released.
Weve achieved the strong exception safety guarantee on our first attempt! But there is some black sticky stuff on our boots.
Tar!
There are problems lying just beneath the surface of this solution. I chose an example that would enable us to pass over the tar pit without sinking too deep. Despite this, weve incurred costs: the line count has doubled, and it takes a lot more effort to understand the code well enough to decide that it works.
If you want to, you may take some time out to convince yourself of the existence of the tar pit Ill wait. Try the analogous example with three pointers to parts or replace the pointers with two objects contained by a value whose assignment operators may throw exceptions. With real life examples, things get very messy very quickly. (If you want a guide to the latter example the Cargill Widget Example I can recommend Herb Sutter [11].)
Many people have reached this point and become discouraged. I agree with them: routinely writing code this way is not reasonable. Too much effort is expended on exception safety housekeeping chores like releasing resources. If you hear that writing exception safe code is hard or that all those try...catch blocks take up too much space, you are listening to someone who has discovered the tar pit.
Im now going to show you how exception handling allows you to use less code (not more), and Im not going to use a single try...catch block for the rest of the article! (In a real program, the exception must be caught somewhere like function a in the discussion above but most functions simply need to let the exceptions pass through safely.)
The Royal Road
There are three golden rules:
- Destructors (and functions that release resources, such as memory) may not propagate exceptions.
- Operations to swap the states of two instances of a class make the no-throw exception safety guarantee.
- An object may own at most one resource.
Weve already met the first rule.
The second rule isnt obvious, but youll see that it provides us a firm footing during the ascent. The idea is that exchanging the states of two objects that own resources is feasible without the need to allocate additional resources. Since nothing needs to be allocated, failure neednt be an option, and consequently there is no need to throw an exception. (It is worth mentioning that the no-throw guarantee is usually not feasible for assignment, which may have to allocate resources.)
If you look at the ISO C++ Language Standard, youll find that std::swap provides the no-throw guarantee for fundamental types and for relevant types in the standard library. This is achieved by overloading std::swap there is a template corresponding to each of the STL containers. This looks like a good way to approach Swap, but introducing additional overloads of std::swap is not permitted by the language standard. The Standard does permit explicit specialization of an existing std::swap template function on user-defined classes, and this is what I would recommend doing where applicable (there is an example below). The standards committee is currently considering a defect report [12] that addresses the problem caused by these rules for the authors of user-defined template classes.
The third rule addresses the cause of all the messy exception-handling code we saw in the last section. It was because creating a new second part might fail that we wrote code to handle it and doubled the number of lines in the assignment operator.
Well now revisit the last example and make use of the above rules. In order to conform to the rule regarding ownership of multiple objects, well delegate the responsibility of resource ownership to a couple of helper classes. Im using the std::auto_ptr<> template to generate the helper classes here because it is standard, not because it is the ideal choice.
class Whole { public: // ...Lots omitted... Whole& operator=(const Whole& rhs); private: std::auto_ptr<PartOne> p1; std::auto_ptr<PartTwo> p2; }; Whole& Whole::operator=(const Whole& rhs) { std::auto_ptr<PartOne> t1(new PartOne(*rhs.p1)); std::auto_ptr<PartTwo> t2(new PartTwo(*rhs.p2)); p1 = t1; p2 = t2; return *this; }Not only is this shorter than the original exception-unsafe example, it meets the strong exception safety guarantee.
Look at why it works:
- There are no leaks: whether the function exits normally or via an exception, t1 and t2 will delete the parts they currently own.
- The assignment expressions cannot throw (first rule).
- The state of the Whole isnt altered until weve done all the things that might throw an exception.
Oh, by the way, Ive not forgotten about self-assignment. Think about it you will see the code works without a test for self-assignment. Such a test may be a bad idea: assuming that self-assignment is very rare in real code and that the branch could have a significant cost, Francis Glassborow suggested a similar style of assignment operator as a speed optimization [13]. Following on from this, Kevlin Henney explored its exception safety aspects in [14] and [6].
We are on much firmer ground than before: it isnt hard to see why the code works and generalizing it is simple. You should be able to see how to manage a Whole with three auto_ptrs to Parts without breaking stride.
You can also see another of the friendly beasts for the first time. Putting the allocation of a resource (here a new expression) into the initializer of a manager object (e.g., auto_ptr<PartOne>) that will delete it on destruction is Resource-Acquisition-Is-Initialization. And, of course, we can once again see Acquisition-Before-Release.
The Assignment Operator A Special Case
Before I go on to deal with having members that may throw when updated, Ive a confession I need to make. It is possible, and usual, to write the assignment operator more simply than the way Ive just demonstrated. The above method is more general than what follows and can be applied when only some aspects of the state are being modified. The canonical form of an assignment operator is:
Whole& Whole::operator=(const Whole& rhs) { Whole copy(rhs); swap(copy); return *this; }Remember the second rule: Whole is a good citizen and provides for Swap (by supplying the swap member function). I also make use of the copy constructor but it would be a perverse class design that supported copy assignment but not copy construction. Im not sure whether the zoologists have determined the relationship between Swap and copying here, but the traveler wont go far wrong in considering Copy-And-Swap as a species in it own right.
For completeness, Ill show the member functions used above:
Whole::Whole(const Whole& rhs) : p1(new PartOne(*rhs.p1)), p2(new PartTwo(*rhs.p2)) { } void Whole::swap(Whole& that) { std::auto_ptr<PartOne> t1(that.p1); that.p1 = p1; p1 = t1; std::auto_ptr<PartTwo> t2(that.p2); that.p2 = p2; p2 = t2; }Note that one must resist the temptation to write std::swap(p1, that.p1) as I did in earlier articles because it isnt guaranteed to work by the language standard. (auto_ptr doesnt have normal copy or assignment semantics thanks are due to Herb Sutter who pointed this problem out.)
One further point about making Whole a good citizen is that we need to specialize std::swap to work through the swap member function. By default, std::swap will use assignment and not deliver the no-throw guarantee we need for Swap. The Standard allows us to specialize existing names in the std namespace on our own types, and it is good practice to do so in the header that defines the type.
namespace std { template<> inline void swap(cuj::Whole& lhs, cuj::Whole& rhs) { lhs.swap(rhs); } }This avoids any unpleasant surprises for client code that attempts to std::swap two Wholes.
Although weve focused on attaining the higher peak of strong exception safety guarantee, weve actually covered all the essential techniques for achieving either strong or basic exception safety. The remainder of the article shows the same techniques being employed in a more complex example and gives some indication of the reasons you might choose to approach the lesser altitudes of basic exception safety.
In Bad Weather
We cant always rely on bright sunshine, or on member variables that are as easy to manipulate as pointers. Sometimes we have to deal with rain and snow, or base classes and member variables with an internal state.
To introduce a more complicated example, Im going to elaborate the Whole class weve just developed by adding member functions that update p1 and p2. Then Ill derive an ExtendedWhole class from it that also contains an instance of another class: PartThree. Well be assuming that operations on PartThree are exception safe, but, for the purposes of discussion, Ill leave it open whether PartThree offers the basic or the strong exception safety guarantee.
Whole& Whole::setP1(const PartOne& value) { p1.reset(new PartOne(value)); return *this; } Whole& Whole::setP2(const PartTwo& value) { p2.reset(new PartTwo(value)); return *this; } class ExtendedWhole : private Whole { public: // Omitted constructors & assignment void swap(const ExtendedWhole& rhs); void setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3); private: int count; PartThree body; };The examples weve looked at so far are a sufficient guide to writing the constructors and assignment operators. We are going to focus on two member functions: the swap member function and a setParts member function that updates the parts.
Writing swap looks pretty easy we just swap the base class and each of the members. Since each of these operations is no-throw, the combination of them is also no-throw.
void ExtendedWhole::swap(ExtendedWhole& rhs) { Whole::swap(rhs); std::swap(count, rhs.count); std::swap(body, rhs.body); }Writing setParts looks equally easy: Whole provides member functions for setting p1 and p2, and we have access to body to set that. Each of these operations is exception safe; indeed the only one that need not make the strong exception safety guarantee is the assignment to body. Think about it for a moment: is this version of setParts exception safe? And does it matter if the assignment to body offers the basic or strong guarantee?
void ExtendedWhole::setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3) { setP1(p1); setP2(p2); body = p3; }Lets go through it together: none of the operations leaks resources, and setParts doesnt allocate any resources, so we dont have any leaks. If an exception propagates from any of the operations, then they leave the corresponding subobject in a useable state, and presumably that leaves ExtendedWhole useable. (It is possible, but in this context implausible, to construct examples where this isnt true.) However, if an exception propagates from setP2 or from the assignment, then the system state has been changed. And this is so regardless of which guarantee PartThree makes.
The easy way to support the strong exception safety guarantee is to ensure that nothing is updated until weve executed all the steps that might throw an exception. The simple approach is to copy the object, make changes to the copy, and swap, as shown below:
void ExtendedWhole::setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3) { ExtendedWhole copy(*this); copy.setP1(p1).setP2(p2); copy.body = p3; swap(copy); }Sadly this is often impractical perhaps because the object doesnt have value semantics (i.e., no copy or assignment). This means taking copies of individual sub-objects and making the changes on the copies, prior to swapping the state between the copies and the original sub-objects:
void ExtendedWhole::setParts( const PartOne& p1, const PartTwo& p2, const PartThree& p3) { Whole copy(*this); copy.setP1(p1).setP2(p2); body = p3; Whole::swap(copy); }Despite its similar appearance, this is harder than the previous example: for example, does it matter if the assignment to body offers the basic or strong guarantee? Yes it does. If it offers the strong guarantee, then all is well with the above; if not, then the assignment needs to be replaced with Copy-And-Swap:
PartThree(p3).swap(body);Once again we have attained the highest peak, but this may not be healthy. On terrestrial mountains above a certain height, there is a death zone where the supply of oxygen is insufficient to support life. Something similar happens with exception safety: there is a cost to implementing the strong exception safety guarantee. Although the code you write isnt much more complicated than the basic version, additional objects are created and these allocate resources at run time. This causes the program to make more use of resources and to spend time allocating and releasing them.
Trying to remain forever at high altitude will drain the vitality. Fortunately, the basic exception safety guarantee is below the death zone: when we make a composite operation whose parts offer this guarantee, we automatically attain the basic guarantee. (As the first version of setParts shows, this is not true of the strong guarantee.) From the basic guarantee, there is an easy climb from this level to the strong guarantee by means of Copy-And-Swap.
Looking Back
Before we descend from the peak of strong exception safety guarantee and return to our starting point, look back over the route we covered. In the distance, you can see the well-trampled path that led us from our first camp to the tar pit. On this side of the tar pit, there are a few tracks made by determined explorers that lead from the tar pit up a treacherous scree slope to where we stand. Off to the left is the easier ridge path weve just ascended that led us by way of basic exception safety guarantee, and beyond the ridge is the road that led us past the tar pit. Fix these landmarks in your mind and remember that the beasts we met are not as fierce as their reputations.
References
[1] Ellis and Stroustrup. The Annotated C++ Reference Manual (Addison-Wesley, 1990).
[2] Dave Abrahams. Exception Safety in STLPort, http://www.stlport.org/doc/exception_safety.html.
[3] H. Muller. Ten Rules for Handling Exception Handling Successfully, C++ Report, January 1996.
[4] Herb Sutter. Designing Exception-Safe Generic Containers, C++ Report, September 1997.
[5] Herb Sutter. More Exception-Safe Generic Containers, C++ Report, November/December 1997.
[6] Kevlin Henney. Creating Stable Assignments, C++ Report, June 1998.
[7] Alan Griffiths. The Safe Path to C++ Exceptions, EXE, December 1999.
[8] Bjarne Stroustrup. The C++ Programming Language, Special Edition (Addison-Wesley, 2000), Appendix E: Standard Library Exception Safety. (This appendix appears only in the Special Edition, but is available on the web at www.research.att.com/~bs/3rd_safe.pdf.)
[9] Dave Abrahams. Exception-Safety in Generic Components. http://people.ne.mediaone.net/abrahams/abrahams.html.
[10] Herb Sutter. Exceptional C++ (Addison Wesley, 2000).
[11] Herb Sutter. Sutters Mill, C++ Report, March 2000.
[12] Dave Abrahams. User Supplied Specializations or Overloads of Namespace std Function Templates, http://anubis.dkuug.dk/JTC1/SC22/WG21/docs/lwg-active.html#226.
[13] Francis Glassborow. The Problem of Self-Assignment, Overload 19, ISSN 1354-3172.
[14] Kevlin Henney. Self Assignment? No Problem! Overload 20, 21, ISSN 1354-3172.
Alan Griffiths works for Experian Ltd. designing software, writing C++ and Java, and mentoring developers. His website is at www.octopull.demon.co.uk/. He also chairs the Association of C and C++ Users (see http://accu.org/ or email info@accu.org). He can be reached at alan.griffiths@uk.experian.com.