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 Widget
s 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 Widget
s, 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
swap
at global scope or in the same namespace as the type T (if T isWidget
in the namespaceWidgetStuff
, compilers will findswap
inWidgetStuff
defined above) - If no T-specific
swap
exists, compilers will useswap
instd
, thanks to theusing
declaration that makesstd::swap
visible in this function.- If there’s a T-specific specialization of
std::swap
, use the specialized version - If not, use the general
swap
template 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 swap
s, non-member swap
s, specializations of std::swap
, and calls to swap
. Below is a good practice on implementing and using customized swap
:
- If the default implementation of
swap
offers 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
swap
member function that efficiently swaps the value of two objects of our type. This function should never throw an exception1 - offer a non-member
swap
in the same namespace as the class or template2. Have it call theswap
member function - if it’s a class (not a class template), specialize
std::swap
for the class. Have it also call theswap
member function
- offer a public
-
One of most useful applications of
swap
is to help classes and class templates offer the strong exception-safety guarantee (See 29 for details). Generally speaking, efficiency and non-exception are twoswap
characteristics that always go hand in hand, because highly efficientswap
s 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
swap
is based on copy construction and copy assignment, and generally both copy functions are allowed to throw exceptions. ↩︎