Item 25: Consider support for a non-throwing swap


Item 25: Consider support for a non-throwing swap

swap is an interesting function. Originally introduced as part of the STL, it's since become a mainstay of exception-safe programming (see Item 29) and a common mechanism for coping with the possibility of assignment to self (see Item 11). Because swap is so useful, it's important to implement it properly, but along with its singular importance comes a set of singular complications. In this Item, we explore what they are and how to deal with them.

To swap the values of two objects is to give each the other's value. By default, swapping is accomplished via the standard swap algorithm. Its typical implementation is exactly what you'd expect:

 namespace std {   template<typename T>          // typical implementation of std::swap;   void swap(T& a, T& b)         // swaps a's and b's values   {     T temp(a);     a = b;     b = temp;   } } 

As long as your types support copying (via copy constructor and copy assignment operator), the default swap implementation will let objects of your types be swapped without your having to do any special work to support it.

However, the default swap implementation may not thrill you. It involves copying three objects: a to temp, b to a, and temp to b. For some types, none of these copies are really necessary. For such types, the default swap puts you on the fast track to the slow lane.

Foremost among such types are those consisting primarily of a pointer to another type that contains the real data. A common manifestation of this design approach is the "pimpl idiom" ("pointer to implementation" see Item 31). A Widget class employing such a design might look like this:

 class WidgetImpl {                          // class for Widget data; public:                                     // details are unimportant   ... private:   int a, b, c;                              // possibly lots of data    std::vector<double> v;                    // expensive to copy!   ... }; class Widget {                              // class using the pimpl idiom public:   Widget(const Widget& rhs);   Widget& operator=(const Widget& rhs)      // to copy a Widget, copy its   {                                         // WidgetImpl object. For    ...                                      // details on implementing    *pImpl = *(rhs.pImpl);                    // operator= in general,    ...                                       // see Items 10, 11, and 12.   }   ... private:   WidgetImpl *pImpl;                         // ptr to object with this };                                           // Widget's data 

To swap the value of two Widget objects, all we really need to do is swap their pImpl pointers, but the default swap algorithm has no way to know that. Instead, it would copy not only three Widgets, but also three WidgetImpl objects. Very inefficient. Not a thrill.

What we'd like to do is tell std::swap that when Widgets are being swapped, the way to perform the swap is to swap their internal pImpl pointers. There is a way to say exactly that: specialize std::swap for Widget. Here's the basic idea, though it won't compile in this form:

 namespace std {   template<>                            // this is a specialized version   void swap<Widget>(Widget& a,          // of std::swap for when T is                     Widget& b)          // Widget; this won't compile   {     swap(a.pImpl, b.pImpl);             // to swap Widgets, just swap   }                                     // their pImpl pointers } 

The "template<>" at the beginning of this function says that this is a total template specialization for std::swap, and the "<Widget>" after the name of the function says that the specialization is for when T is Widget. In other words, when the general swap template is applied to Widgets, this is the implementation that should be used. In general, we're not permitted to alter the contents of the std namespace, but we are allowed to totally specialize standard templates (like swap) for types of our own creation (such as Widget). That's what we're doing here.

As I said, though, this function won't compile. That's because it's trying to access the pImpl pointers inside a and b, and they're private. We could declare our specialization a friend, but the convention is different: it's to have Widget declare a public member function called swap that does the actual swapping, then specialize std::swap to call the member function:

 class Widget {                     // same as above, except for the public:                            // addition of the swap mem func   ...   void swap(Widget& other)   {     using std::swap;               // the need for this declaration                                    // is explained later in this Item     swap(pImpl, other.pImpl);      // to swap Widgets, swap their   }                                // pImpl pointers   ... }; namespace std {   template<>                       // revised specialization of   void swap<Widget>(Widget& a,     // std::swap                     Widget& b)   {     a.swap(b);                     // to swap Widgets, call their   }                                // swap member function } 

Not only does this compile, it's also consistent with the STL containers, all of which provide both public swap member functions and specializations of std::swap that call these member functions.

Suppose, however, that Widget and WidgetImpl were class templates instead of classes, possibly so we could parameterize the type of the data stored in WidgetImpl:

 template<typename T> class WidgetImpl { ... }; template<typename T> class Widget { ... }; 

Putting a swap member function in Widget (and, if we need to, in WidgetImpl) is as easy as before, but we run into trouble with the specialization for std::swap. This is what we want to write:

 namespace std {   template<typename T>   void swap<Widget<T> >(Widget<T>& a,      // error! illegal code!                         Widget<T>& b)   { a.swap(b); } } 

This looks perfectly reasonable, but it's not legal. We're trying to partially specialize a function template (std::swap), but though C++ allows partial specialization of class templates, it doesn't allow it for function templates. This code should not compile (though some compilers erroneously accept it).

When you want to "partially specialize" a function template, the usual approach is to simply add an overload. That would look like this:

 namespace std {   template<typename T>             // an overloading of std::swap   void swap(Widget<T>& a,          // (note the lack of "<...>" after             Widget<T>& b)          // "swap"), but see below for   { a.swap(b); }                   // why this isn't valid code } 

In general, overloading function templates is fine, but std is a special namespace, and the rules governing it are special, too. It's okay to totally specialize templates in std, but it's not okay to add new templates (or classes or functions or anything else) to std. The contents of std are determined solely by the C++ standardization committee, and we're prohibited from augmenting what they've decided should go there. Alas, the form of the prohibition may dismay you. Programs that cross this line will almost certainly compile and run, but their behavior is undefined. If you want your software to have predictable behavior, you'll not add new things to std.

So what to do? We still need a way to let other people call swap and get our more efficient template-specific version. The answer is simple. We still declare a non-member swap that calls the member swap, we just don't declare the non-member to be a specialization or overloading of std::swap. For example, if all our Widget-related functionality is in the namespace WidgetStuff, it would look like this:

 namespace WidgetStuff {   ...                                     // templatized WidgetImpl, etc.   template<typename T>                    // as before, including the swap   class Widget { ... };                   // member function   ...   template<typename T>                    // non-member swap function;   void swap(Widget<T>& a,                 // not part of the std namespace             Widget<T>& b)                                            {     a.swap(b);   } } 

Now, if any code anywhere calls swap on two Widget objects, the name lookup rules in C++ (specifically the rules known as argument-dependent lookup or Koenig lookup) will find the Widget-specific version in WidgetStuff. Which is exactly what we want.

This approach works as well for classes as for class templates, so it seems like we should use it all the time. Unfortunately, there is a reason for specializing std::swap for classes (I'll describe it shortly), so if you want to have your class-specific version of swap called in as many contexts as possible (and you do), you need to write both a non-member version in the same namespace as your class and a specialization of std::swap.

By the way, if you're not using namespaces, everything above continues to apply (i.e., you still need a non-member swap that calls the member swap), but why are you clogging the global namespace with all your class, template, function, enum, enumerant, and typedef names? Have you no sense of propriety?

Everything I've written so far pertains to authors of swap, but it's worth looking at one situation from a client's point of view. Suppose you're writing a function template where you need to swap the values of two objects:

 template<typename T> void doSomething(T& obj1, T& obj2) {   ...   swap(obj1, obj2);   ... } 

Which swap should this call? The general one in std, which you know exists; a specialization of the general one in std, which may or may not exist; or a T-specific one, which may or may not exist and which may or may not be in a namespace (but should certainly not be in std)? What you desire is to call a T-specific version if there is one, but to fall back on the general version in std if there's not. Here's how you fulfill your desire:

 template<typename T> void doSomething(T& obj1, T& obj2) {   using std::swap;           // make std::swap available in this function   ...   swap(obj1, obj2);          // call the best swap for objects of type T   ... } 

When compilers see the call to swap, they search for the right swap to invoke. C++'s name lookup rules ensure that this will find any T-specific swap at global scope or in the same namespace as the type T. (For example, if T is Widget in the namespace WidgetStuff, compilers will use argument-dependent lookup to find swap in WidgetStuff.) If no T-specific swap exists, compilers will use swap in std, thanks to the using declaration that makes std::swap visible in this function. Even then, however, compilers will prefer a T-specific specialization of std::swap over the general template, so if std::swap has been specialized for T, the specialized version will be used.

Getting the right swap called is therefore easy. The one thing you want to be careful of is to not qualify the call, because that will affect how C++ determines the function to invoke. For example, if you were to write the call to swap this way,

 std::swap(obj1, obj2);         // the wrong way to call swap 

you'd force compilers to consider only the swap in std (including any template specializations), thus eliminating the possibility of getting a more appropriate T-specific version defined elsewhere. Alas, some misguided programmers do qualify calls to swap in this way, and that's why it's important to totally specialize std::swap for your classes: it makes type-specific swap implementations available to code written in this misguided fashion. (Such code is present in some standard library implementations, so it's in your interest to help such code work as efficiently as possible.)

At this point, we've discussed the default swap, member swaps, non-member swaps, specializations of std::swap, and calls to swap, so let's summarize the situation.

First, if the default implementation of swap offers acceptable efficiency for your class or class template, you don't need to do anything. Anybody trying to swap objects of your type will get the default version, and that will work fine.

Second, if the default implementation of swap isn't efficient enough (which almost always means that your class or template is using some variation of the pimpl idiom), do the following:

  1. Offer a public swap member function that efficiently swaps the value of two objects of your type. For reasons I'll explain in a moment, this function should never throw an exception.

  2. Offer a non-member swap in the same namespace as your class or template. Have it call your swap member function.

  3. If you're writing a class (not a class template), specialize std::swap for your class. Have it also call your swap member function.

Finally, if you're calling swap, be sure to include a using declaration to make std::swap visible in your function, then call swap without any namespace qualification.

The only loose end is my admonition to have the member version of swap never throw exceptions. That's because one of the most useful applications of swap is to help classes (and class templates) offer the strong exception-safety guarantee. Item 29 provides all the details, but the technique is predicated on the assumption that the member version of swap never throws. This constraint applies only to the member version! It can't apply to the non-member version, because the default version of swap is based on copy construction and copy assignment, and, in general, both of those functions are allowed to throw exceptions. When you write a custom version of swap, then, you are typically offering more than just an efficient way to swap values; you're also offering one that doesn't throw exceptions. As a general rule, these two swap characteristics go hand in hand, because highly efficient swaps are almost always based on operations on built-in types (such as the pointers underlying the pimpl idiom), and operations on built-in types never throw exceptions.

Things to Remember

  • Provide a swap member function when std::swap would be inefficient for your type. Make sure your swap doesn't throw exceptions.

  • If you offer a member swap, also offer a non-member swap that calls the member. For classes (not templates), specialize std::swap, too.

  • When calling swap, employ a using declaration for std::swap, then call swap without namespace qualification.

  • It's fine to totally specialize std templates for user-defined types, but never try to add something completely new to std.




Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
ISBN: 321334876
EAN: N/A
Year: 2006
Pages: 102

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