GotW #59

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Exception-Safe Class Design, Part 1: Copy Assignment 
Difficulty: 7 / 10

Is it possible to make any C++ class strongly exception-safe, for example for its copy assignment operator? If so, how? What are the issues and consequences?

Problem

JG Questions

1. What are the three common levels of exception safety? Briefly explain each one and why it is important.

2. What is the canonical form of strongly exception-safe copy assignment?

Guru Questions

3. Consider the following class:

    //  Example 3: The Cargill Widget Example
    //
    class Widget
    {
      // ...
    private:
      T1 t1_;
      T2 t2_;
    };

Assume that any T1 or T2 operation might throw. Without changing the structure of the class, is it possible to write a strongly exception-safe Widget::operator=( const Widget& )? Why or why not? Draw conclusions.

4. Describe and demonstrate a simple transformation that works on any class in order to make strongly exception-safe copy assignment possible and easy for that class. Where have we seen this transformation technique before in other contexts? Cite GotW issue number(s).

Solution

Review: Exception Safety Canonical Forms

1. What are the three common levels of exception safety? Briefly explain each one and why it is important.

The canonical Abrahams Guarantees are as follows.

1. Basic Guarantee: If an exception is thrown, no resources are leaked, and objects remain in a destructible and usable -- but not necessarily predictable -- state. This is the weakest usable level of exception safety, and is appropriate where client code can cope with failed operations that have already made changes to objects' state.

2. Strong Guarantee: If an exception is thrown, program state remains unchanged. This level always implies global commit-or-rollback semantics, including that no references or iterators into a container be invalidated if an operation fails.

In addition, certain functions must provide an even stricter guarantee in order to make the above exception safety levels possible:

3. Nothrow Guarantee: The function will not emit an exception under any circumstances. It turns out that it is sometimes impossible to implement the strong or even the basic guarantee unless certain functions are guaranteed not to throw (e.g., destructors, deallocation functions). As we will see below, an important feature of the standard auto_ptr is that no auto_ptr operation will throw.

2. What is the canonical form of strongly exception-safe copy assignment?

It involves two steps: First, provide a nonthrowing Swap() function that swaps the guts (state) of two objects:

  void T::Swap( T& other ) throw()
  {
    // ...swap the guts of *this and other...
  }

Second, implement operator=() using the "create a temporary and swap" idiom:

  T& T::operator=( const T& other )
  {
    T temp( other ); // do all the work off to the side
    Swap( temp );    // then "commit" the work using
    return *this;    //  nonthrowing operations only
  }

The Cargill Widget Example

This brings us to the Guru questions, starting with a new exception safety challenge proposed by Tom Cargill:

3. Consider the following class:

    //  Example 3: The Cargill Widget Example
    //
    class Widget
    {
      // ...
    private:
      T1 t1_;
      T2 t2_;
    };

Assume that any T1 or T2 operation might throw. Without changing the structure of the class, is it possible to write a strongly exception-safe Widget::operator=( const Widget& )? Why or why not? Draw conclusions.

Short answer: In general, no, it can't be done without changing the structure of Widget.

In the Example 3 case, it's not possible to write a strongly exception-safe Widget::operator=() because there's no way that we can change the state of both of the t1_ and t2_ members atomically. Say that we attempt to change t1_, then attempt to change t2_. The problem is twofold:

1. If the attempt to change t1_ throws, t1_ must be unchanged. That is, to make Widget::operator=() strongly exception-safe relies fundamentally on the exception safety guarantees provided by T1, namely that T1::operator=() (or whatever mutating function we are using) either succeeds or does not change its target. This comes close to requiring the strong guarantee of T1::operator=(). (The same reasoning applies to T2::operator=().)

2. If the attempt to change t1_ succeeds, but the attempt to change t2_ throws, we've entered a "halfway" state and cannot in general roll back the change already made to t1_.

Therefore, the way Widget is currently structured, its operator=() cannot be made strongly exception-safe.

Note also that Cargill's Widget Example isn't all that different from the following simpler case:

  class Widget2  
  {              
    // ...       
  private:       
    T1 t1_;      
  };             

In the above code, problem #1 above still exists. If T1::operator=() can throw in such a way that it has already started to modify the target, there is no way to write a strongly exception-safe Widget2::operator=() unless T1 provides suitable facilities through some other function (but if T1 can do that, why doesn't it for T1::operator=()?).

Our goal: To write a Widget::operator=() that is strongly exception-safe, without making any assumptions about the exception safety of any T1 or T2 operation. Can it be done? Or is all lost?

A Complete Solution: Using the Pimpl Idiom

The good news is that, even though Widget::operator=() can't be made strongly exception-safe without changing Widget's structure, the following simple transformation always works:

4. Describe and demonstrate a simple transformation that works on any class in order to make strongly exception-safe copy assignment possible and easy for that class. Where have we seen this transformation technique before in other contexts? Cite GotW issue number(s).

The way to solve the problem is hold the member objects by pointer instead of by value, preferably all behind a single pointer with a Pimpl transformation (described in GotW issues like 7, 15, 24, 25, and 28).

Here is the canonical Pimpl exception-safety transformation:

  //  Example 4: The canonical solution to
  //             Cargill's Widget Example
  //
  class Widget
  {
    Widget();  // initializes pimpl_ with new WidgetImpl
    ~Widget(); // must be provided, because the implicit
               //  version causes usage problems (see GotW #62)
    // ...
  private:
    class WidgetImpl;
    auto_ptr<WidgetImpl> pimpl_;
    // ... provide copy construction and assignment
    //     that work correctly, or suppress them ...
  };
  class Widget::WidgetImpl
  {
  public:
    // ...
    T1 t1_;
    T2 t2_;
  };

Now we can easily implement a nonthrowing Swap(), which means we can easily implement exception-safe copy assignment: First, provide the nonthrowing Swap() function that swaps the guts (state) of two objects (note that this function can provide the nothrow guarantee because no auto_ptr operations are permitted to throw exceptions):

  void Widget::Swap( Widget& other ) throw()
  {
    auto_ptr<WidgetImpl> temp( pimpl_ );
    pimpl_ = other.pimpl_;
    other.pimpl_ = temp;
  }

Second, implement the canonical form of operator=() using the "create a temporary and swap" idiom:

  Widget& Widget::operator=( const Widget& other )
  {
    Widget temp( other ); // do all the work off to the side
    Swap( temp );    // then "commit" the work using
    return *this;    //  nonthrowing operations only
  }

A Potential Objection, and Why It's Unreasonable

Some may object: "Aha! Therefore this proves exception safety is unattainable in general, because you can't solve the general problem of making any arbitrary class strongly exception-safe without changing the class!"

Such a position is unreasonable and untenable. The Pimpl transformation, a minor structural change, IS the solution to the general problem. To say, "no, you can't do that, you have to be able to make an arbitrary class exception-safe without any changes," is unreasonable for the same reason that "you have to be able to make an arbitrary class meet New Requirement #47 without any changes" is unreasonable.

For example:

Unreasonable Statement #1: "Polymorphism doesn't work in C++ because you can't make an arbitrary class usable in place of a Base& without changing it (to derive from Base)."

Unreasonable Statement #2: "STL containers don't work in C++ because you can't make an arbitrary class usable in an STL container without changing it (to provide an assignment operator)."

Unreasonable Statement #3: "Exception safety doesn't work in C++ because you can't make an arbitrary class strongly exception-safe without changing it (to put the internals in a Pimpl class)."

Clearly all the above arguments are equally bankrupt, and the Pimpl transformation is indeed the general solution to strongly exception-safe objects.

So, what have we learned?

Conclusion 1: Exception Safety Affects a Class's Design

Exception safety is never "just an implementation detail." The Pimpl transformation is a minor structural change, but still a change. GotW #8 shows another example of how exception safety considerations can affect the design of a class's member functions.

Conclusion 2: You Can Always Make Your Code (Nearly) Strongly Exception-Safe

There's an important principle here:

Just because a class you use isn't in the least exception-safe is no reason that YOUR code that uses it can't be (nearly) strongly exception-safe.

Anybody can use a class that lacks a strongly exception-safe copy assignment operator and make that use exception-safe. The "hide the details behind a pointer" technique can be done equally well by either the Widget implementor or the Widget user... it's just that if it's done by the implementor it's always safe, and the user won't have to do this:

  class MyClass
  {
    auto_ptr<Widget> w_; // hold the unsafe-to-copy
                         //  Widget at arm's length
  public:
    void Swap( MyClass& other ) throw()
    {
      auto_ptr<Widget> temp( w_ );
      w_ = other.w_;
      other.w_ = temp;
    }
    MyClass& operator=( const MyClass& other )
    {
      /* canonical form */
    }
    // ... destruction, copy construction,
    //     and copy assignment ...
  };

Conclusion 3: Use Pointers Judiciously

To quote Scott Meyers:

"When I give talks on EH, I teach people two things:

"- POINTERS ARE YOUR ENEMIES, because they lead to the kinds of problems that auto_ptr is designed to eliminate.

To wit, bald pointers should normally be owned by manager objects that own the pointed-at resource and perform automatic cleanup. Then Scott continues:

"- POINTERS ARE YOUR FRIENDS, because operations on pointers can't throw.

"Then I tell them to have a nice day :-)

Scott captures a fundamental dichotomy well. Fortunately, in practice you can and should get the best of both worlds:

- USE POINTERS BECAUSE THEY ARE YOUR FRIENDS, because operations on pointers can't throw.

- KEEP THEM FRIENDLY BY WRAPPING THEM IN MANAGER OBJECTS like auto_ptrs, because this guarantees cleanup. This doesn't compromise the nonthrowing advantages of pointers because auto_ptr operations never throw either (and you can always get at the real pointer inside an auto_ptr whenever you need to).

Indeed, often the best way to implement the Pimpl idiom is exactly as shown in Example 4 above, by using a pointer (in order to take advantage of nonthrowing operations) while still wrapping the dynamic resource safely in an auto_ptr manager object. Just remember that now your object must provide its own copy construction and assignment with the right semantics for the auto_ptr member, or disable them if copy construction and assignment don't make sense for the class.

Copyright © 2009 Herb Sutter