When std::swap would be inefficient for your type,provide a non-throwing swap member function, a non-member swap calling the member, and possibly a specialized std::swap for the case of classes (not templates).
swap, since its introduction into STL, is useful for exception-safe programming (item 29) and a common mechanism for coping with the possibility of assignment to self (item 11). Due to its importance, it should be implemented properly, which is exactly what this item explores about.
Customization
By default, swapping is accomplished via the standard swap algorithm:
|
|
As long as our types support copying (via copy constructor and copy assignment operator), the default swap implementation will work. However, for some types, none of these copies are really necessary. For example: there’s a common design manifestation called “pimpl idiom” (“pointer to implementation”, item 31) that consisting primarily of a pointer to another type that contains the real data:
|
|
|
|
To swap the value of two Widget objects, all we need to do is swap their pImpl pointers instead of copying three Widgets as well as three underlying WidgetImpl objects. In order to let default swap know this information, we need to specialize std::swap for Widget:
namespace std {
template<> // a specialized version of std::swap
void swap
{
swap(a.pImpl, b.pImpl); // won’t compile here
}
}
The template<> at the begining 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, so compilter knows that when the general swap template is applied to Widgets, this is the implementation to use - although we are not allowed to alter the contents of the std namespace, it is totally fine to specialize standard templates (like swap) for our own types (such as Widget).
However, this implementation won’t compile, because we can’t access the private pImpl pointers inside a and b. To solve the problem, we declare a public member function called swap that does the actual swapping, then specialize std::swap to call the member function:
|
|
This implementation will compile and be consistent with the STL container. However, if the Widget and WidgetImpl were class template instead of classes (so that we could parameterize the type of the data stored in WidgetImpl), things get more complicated:
|
|
It is still easy to put a swap member function inside Widget the same way as before, but there’s a trouble with the specialization for std::swap:
|
|
Apparently we’re partially specializing a function template std::swap here, and the problem is that, although C++ allows partial specialization of class templates, it doesn’t allow it for function templates (though some compilers may erroneously accept it).
The usual approach to “partially specialize” a function template is to add an overload like this:
|
|
However, rather than overloading a common function template, what we do here is overloading a function template std::swap in the special std namesapce, where it’s not allowed to add new templates or classes or functions or anything else into it (the contens of std are determined solely by the C++ standardization committee). Even though programs that cross the line will almost certainly compile and run, their behavior is undefined.
Thus in this case, not to declare the non-member function to be a specialization or overloading of std::swap, we just make a normal non-member swap function in Widget-related namespace:
|
|
The name lookup rules in C++ (specifically the rules known as argument-dependent lookup or Koenig lookup) will guarantee the Widget-specific version of swap in WidgetStuff will be invoked if any code calls swap on two Widget objects.
Usage
Let’s look from a client’s point of view and see how to use the swap. Ideally, we want to call a T-specific version of swap if there is one, but to fall back on the general version in std if there’s not. To fulfill this idea:
|
|
When compilers see the call to swap, they search for the best swap to invoke - according to C++’s name lookup rules, it follows the order below:
- Find any T-specific
swapat global scope or in the same namespace as the type T (if T isWidgetin the namespaceWidgetStuff, compilers will findswapinWidgetStuffdefined above) - If no T-specific
swapexists, compilers will useswapinstd, thanks to theusingdeclaration that makesstd::swapvisible in this function.- If there’s a T-specific specialization of
std::swap, use the specialized version - If not, use the general
swaptemplate function.
- If there’s a T-specific specialization of
One thing worth noting is to not qualify the call like this:
|
|
here we 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 (or even some standard library) do qualify calls to swap in this way. To make code work as efficiently as possible, we’d better totally specialize std::swap for our classes.
Summary
We’ve discussed the default swap, member swaps, non-member swaps, specializations of std::swap, and calls to swap. Below is a good practice on implementing and using customized swap:
- If the default implementation of
swapoffers acceptable efficiency for our class or class template, nothing needs to be done to specialize the defaultstd::swap. - If not (for class or template using some variation of the pimpl idiom):
- offer a public
swapmember function that efficiently swaps the value of two objects of our type. This function should never throw an exception1 - offer a non-member
swapin the same namespace as the class or template2. Have it call theswapmember function - if it’s a class (not a class template), specialize
std::swapfor the class. Have it also call theswapmember function
- offer a public
-
One of most useful applications of
swapis to help classes and class templates offer the strong exception-safety guarantee (See 29 for details). Generally speaking, efficiency and non-exception are twoswapcharacteristics that always go hand in hand, because highly efficientswaps 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. ↩︎ -
The non-exception constraint can’t apply to the non-member version, because the default version of
swapis based on copy construction and copy assignment, and generally both copy functions are allowed to throw exceptions. ↩︎