ptg7544714
ptg7544714
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 {
swap(a.pImpl, b.pImpl); // to swap Widgets, swap their
} // pImpl pointers; this won’t
compile }
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 Wid- get. In other words, when the general swap template is applied to Wid- gets, 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 try- ing to access the pImpl pointers inside a and b, and they’re private. We could declare our specialization a friend, but the convention is differ- ent: it’s to have Widget declare a public member function called swap
ptg7544714 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 contain- ers, all of which provide both public swap member functions and ver- sions 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 Wid- getImpl) is as easy as before, but we run into trouble with the special- ization 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); }
}
ptg7544714 This looks perfectly reasonable, but it’s not legal. We’re trying to par-
tially 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 erro- neously 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 tem- plates (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);
} }
ptg7544714 Now, if any code anywhere calls swap on two Widget objects, the name
lookup rules in C++ (specifically the rules known as argument-depen- dent 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 rea- son 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-mem- ber 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 contin- ues 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 ...
}
ptg7544714 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-spe- cific 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. Any- body 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 mo- ment, 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.
ptg7544714 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 mem- ber version! It can’t apply to the non-member version, because the default version of swap is based on copy construction and copy assign- ment, 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 opera- tions 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.
ptg7544714
Implementations
For the most part, coming up with appropriate definitions for your classes (and class templates) and appropriate declarations for your functions (and function templates) is the lion’s share of the battle.
Once you’ve got those right, the corresponding implementations are largely straightforward. Still, there are things to watch out for. Defin- ing variables too soon can cause a drag on performance. Overuse of casts can lead to code that’s slow, hard to maintain, and infected with subtle bugs. Returning handles to an object’s internals can defeat encapsulation and leave clients with dangling handles. Failure to con- sider the impact of exceptions can lead to leaked resources and cor- rupted data structures. Overzealous inlining can cause code bloat.
Excessive coupling can result in unacceptably long build times.
All of these problems can be avoided. This chapter explains how.