Solution

I l @ ve RuBoard

graphics/bulb.gif

1. As you continue to root through the archives, you see that someone must not have liked Item 20 Example 20-2 because later versions of the files in question were changed as follows :

  // Example 21-1   //   // In some header file:   void f( auto_ptr<T1>, auto_ptr<T2> );   // In some implementation file:   f( auto_ptr<T1>( new T1 ), auto_ptr<T2>( new T2 ) );  

What improvements does this version offer over Item 20 Example 20-2, if any? Do any exception safety problems remain ? Explain.

This code attempts to "throw [9] auto_ptr at the problem." Many people believe that a smart pointer like auto_ptr is an exception-safety panacea, a touchstone or amulet that by its mere presence somewhere nearby can help ward off compiler indigestion.

[9] Pun intended.

It is not. Nothing has changed. Example 21-1 is still not exception-safe, for exactly the same reasons as before.

Specifically, the problem is that the resources are safe only if they really make it into a managing auto_ptr , but the same problems already noted can still occur before either auto_ptr constructor is ever reached. This is because both of the two problematic execution orders mentioned earlier are still possible, but now with the auto_ptr constructors tacked onto the end before f() . For one example:

  1. allocate memory for the T1

  1. construct the T1

  2. allocate memory for the T2

  3. construct the T2

  4. construct the auto_ptr<T1>

  5. construct the auto_ptr<T2>

  6. call f()

In the above case, the same problems are still present if either of steps 3 or 4 throws. Similarly:

  1. allocate memory for the T1

  2. allocate memory for the T2

  3. construct the T1

  4. construct the T2

  5. construct the auto_ptr<T1>

  6. construct the auto_ptr<T2>

  7. call f()

Again, the same problems are present if either step 3 or step 4 throws.

Fortunately, though, this is not a problem with auto_ptr ; auto_ptr is being used the wrong way, that's all. In a moment, we'll see several ways to use it better.

Aside: A Non-Solution

Note that the following is not a solution:

 // In some header file: void f( auto_ptr<T1> = auto_ptr<T1>( new T1 ),         auto_ptr<T2> = auto_ptr<T2>( new T2 ) ); // In some implementation file: f(); 

Why is this code not a solution? Because it's identical to Example 21-1 in terms of expression evaluation. Default arguments are considered to be created in the function call expression, even though they're written somewhere else entirely (in the function declaration).

A Limited Solution

2. Demonstrate how to write an auto_ptr_new facility that solves the safety problems in Question 1 and can be invoked as follows:

  // Example 21-2   //   // In some header file:   void f( auto_ptr<T1>, auto_ptr<T2> );   // In some implementation file:   f( auto_ptr_new<T1>(), auto_ptr_new<T2>() );  

The simplest solution is to provide a function template like the following:

 // Example 21-2(a): Partial solution // template<typename T> auto_ptr<T> auto_ptr_new() {   return auto_ptr<T>( new T ); } 

This solves the exception safety problems. No sequence of generated code can cause resources to be leaked, because now all we have is two functions, and we know that one must be executed entirely before the other. Consider the following evaluation order:

  1. call auto_ptr_new<T1>()

  2. call auto_ptr_new<T2>()

If step 1 throws, there are no leaks because the auto_ptr_new() template is itself strongly exception-safe.

If step 2 throws, then is the temporary auto_ptr<T1> created by step 1 guaranteed to be cleaned up? Yes, it is. One might wonder : Isn't this pretty much the same as the new T1 object created in the corresponding case in Item 20 Example 20-2, which isn't correctly cleaned up? No, this time it's not quite the same, because here the auto_ptr<T1> is actually a temporary object, and cleanup of temporary objects is correctly specified in the standard. From the standard, in 12.2/3:

Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created . This is true even if that evaluation ends in throwing an exception .

But Example 21-2(a) is a limited solution: It only works with a default constructor, which breaks if a given type T doesn't have a default constructor, or if you don't want to use it. A more general solution is still needed.

Generalizing the auto_ptr_new() Solution

As pointed out by Dave Abrahams, we can extend the solution to support non-default constructors by providing a family of overloaded function templates:

 // Example 21-2(b): Improved solution // template<typename T> auto_ptr<T> auto_ptr_new() {   return auto_ptr<T>( new T ); } template<typename T, typename Arg1> auto_ptr<T> auto_ptr_new( const Arg1& arg1 ) {   return auto_ptr<T>( new T( arg1 ) ); } template<typename T, typename Arg1, typename Arg2> auto_ptr<T> auto_ptr_new( const Arg1& arg1,                           const Arg2& arg2 ) {   return auto_ptr<T>( new T( arg1, arg2 ) ); } // etc. 

Now auto_ptr_new() fully and naturally supports non-default construction.

A Better Solution

Although auto_ptr_new() is nice, is there any way we could have avoided all the exception-safety problems without writing such helper functions? Could we have avoided the problems with better coding standards? Yes, and here is one possible standard that would have eliminated the problem: Never allocate resources (for example, via new ) in the same expression as any other code that could throw an exception . This applies even if the new' d resource will immediately be managed (for example, passed to an auto_ptr constructor) in the same expression .

In the Example 21-1 code, the way to satisfy this guideline is to move one of the temporary auto_ptr s into a separate named variable:

 // Example 21-1(a): A solution // // In some header file: void f( auto_ptr<T1>, auto_ptr<T2> ); // In some implementation file: {   auto_ptr<T1> t1( new T1 );   f( t1, auto_ptr<T2>( new T2 ) ); } 

This satisfies guideline #1 because, although we are still allocating a resource, it can't be leaked because of an exception, because it's not created in the same expression as any other code that could throw. [10]

[10] I'm being deliberately, but only slightly, fuzzy, because although the body of f() is included in the expression evaluation, we don't care whether it throws.

Here is another possible coding standard, which is even simpler and easier to get right (and easier to catch in code reviews): Perform every explicit resource allocation (for example, new ) in its own code statement, which immediately gives the new ' d resource to a manager object (for example, auto_ptr ) .

In Example 21-1, the way to satisfy the second alternative guideline is to move both of the temporary auto_ptr s into separate named variables :

 // Example 21-1(b): A simpler solution // // In some header file: void f( auto_ptr<T1>, auto_ptr<T2> ); // In some implementation file: {   auto_ptr<T1> t1( new T1 );   auto_ptr<T2> t2( new T2 );   f( t1, t2 ); } 

This satisfies guideline #2, and it required a lot less thought to get it right. Each new resource is created in its own expression and is immediately given to a managing object.

Summary

My recommendation is:

Guideline

graphics/guideline.gif

Perform every explicit resource allocation (for example, new ) in its own code statement, which immediately gives the new 'd resource to a manager object (for example, auto_ptr ).


This guideline is easy to understand and remember, it neatly avoids all the exception safety problems in the original problem, and by mandating the use of manager objects, it helps to avoid many other exception safety problems as well. This guideline is a good candidate for inclusion in your team's coding standards.

Acknowledgments

This Item was prompted by a discussion thread on comp.lang.c++. moderated . This solution draws on observations presented by James Kanze, Steve Clamage, and Dave Abrahams in that and other threads, and in private correspondence.

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net