Effective C++ item 25: Consider Support For a Non-throwing swap

How to write a swap functon for your own class? This is especially useful when default swap behavior is too heavy for your class. You can imagine this is how std‘s implementation

1
2
3
4
5
6
7
8
9
namespace std {
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}

And if you have any classes implement pimpl idiom like such

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class WidgetImpl {
public:
...
private:
int a, b, c;
std::vector<double> v;
...
};

class Widget {
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
...

private:
WidgetImpl *pImpl;
};

If you want to swap such object, you only need to swap the implementation pointer instead of the whole object. That’s when you need to write your own swap method. Most likely swap needs access to private members, so you will write a member swap method

1
2
3
4
5
6
7
8
9
10
class Widget {
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
...
};

And if you want to support conventional std::swap function, you can add a specialized version for std::swap which calls the member swap as such

1
2
3
4
5
6
7
8
namespace std {
template<>
void swap<Widget>(Widget& a,
Widget& b)
{
a.swap(b);
}
}

You may wonder why we need using std::swap in above member swap example. In above case, it seems not necessary because we know pImpl is pointer type and std::swap supports such. So you could use std::swap directly. But above way of calling swap works for all cases. Let’s discuss what’s the special case we need to consider.

You may have guessed it, it’s when user defined class is a template class. When you make Widget and WidgetImpl class templates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename T>
class WidgetImplTP {
private:
std::vector<T> v;
};

template<typename T>
class WidgetTP {
public:
WidgetTP &operator=(const WidgetTP &rhs) {
*pImpl = *(rhs.pImpl);
return *this;
}

void swap(WidgetTP &other) {
std::cout << "WidgetTP::swap\n";
using std::swap;
swap(pImpl, other.pImpl);
}

private:
WidgetImplTP<T> *pImpl;
};

You would natually provide following specialized std::swap implementation

1
2
3
4
5
6
namespace std {
template<typename T>
void swap<WidgetTP<T> >(WidgetTP<T> &a, WidgetTP<T> &b) {
a.swap(b);
}
}

This won’t compile because you cannot partially specialize a function template error: function template partial specialization is not allowed.

The common solution is to use overloading instead of total template specialization. But there is a problem doing so in std namespace. 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. Voilating such rule will lead to undefined behavior.

So what’s the solution to this? Creating a namespace and define a free swap there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace WidgetStuff {
template<typename T>
class WidgetImplTP {
private:
std::vector<T> v;
};

template<typename T>
class WidgetTP {
public:
WidgetTP &operator=(const WidgetTP &rhs) {
*pImpl = *(rhs.pImpl);
return *this;
}

void swap(WidgetTP &other) {
std::cout << "WidgetTP::swap\n";
using std::swap;
swap(pImpl, other.pImpl);
}

private:
WidgetImplTP<T> *pImpl;
};

template<typename T>
void swap(WidgetTP<T> &a, WidgetTP<T> &b) {
std::cout << "WidgetTP::swap(WidgetTP<T>& a, WidgetTP<T>& b)\n";
a.swap(b);
}
}

For any user of swap who may or may not know whether the class implements it’s own swap, always bring std::swap into scope by doing using std::swap and then call swap. This way, it will call cusomized swap function if it’s available, otherwise, it will call std::swap.

1
2
3
4
5
6
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
}

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:

  • 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.
  • Offer a non-member swap in the same namespace as your class or template. Have it call your swap member function.
  • 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.

Reference:
“Effective C++” Third Edition by Scott Meyers.