More C++ Idioms/Non-throwing swap
Non-throwing swap
[edit | edit source]Intent
[edit | edit source]- To implement an exception safe and efficient swap operation.
- To provide uniform interface to it to facilitate generic programming.
Also Known As
[edit | edit source]- Exception safe swap
Motivation
[edit | edit source]A typical implementation of swap could be given as follows:
template<class T>
void swap (T &a, T &b)
{
T temp (a);
a = b;
b = temp;
}
This can be problematic for two reasons:
- Performance
- Swapping of two large, complex objects of the same type is inefficient due to acquisition and release of resources for the intermediate temporary object.
- Exception-safety
- This generic swap implementation may throw if resources are not available. (Such a behavior does not make sense where in fact no new resources should have been requested in the first place.) Therefore, this implementation cannot be used for the Copy-and-swap idiom.
Solution and Sample Code
[edit | edit source]Non-throwing swap idiom uses Handle Body idiom to achieve the desired effect. The abstraction under consideration is split between two implementation classes. One is handle and other one is body. The handle holds a pointer to a body object. The swap is implemented as a simple swap of pointers, which are guaranted to not throw exceptions and it is very efficient as no new resources are acquired or released.
namespace Orange {
class String
{
char * str;
public:
void swap (String &s) // noexcept
{
std::swap (this->str, s.str);
}
};
}
Although an efficient and exception-safe swap function can be implemented (as shown above) as a member function, non-throwing swap idiom goes further than that for simplicity, consistency, and to facilitate generic programming. An explicit specialization of std::swap should be added in the std namespace as well as the namespace of the class itself.
namespace Orange { // namespace of String
void swap (String & s1, String & s2) // noexcept
{
s1.swap (s2);
}
}
namespace std {
template <>
void swap (Orange::String & s1, Orange::String & s2) // noexcept
{
s1.swap (s2);
}
}
Adding it in two places takes care of two different common usage styles of swap (1) unqualified swap (2) fully qualified swap (e.g., std::swap). When unqualified swap is used, right swap is looked up using Koenig lookup (provided one is already defined). If fully qualified swap is used, Koenig lookup is suppressed and one in the std namespace is used instead. It is a very common practice. Remaining discussion here uses fully qualified swap only. It gives a uniform look and feel because C++ programmers often use swap function in an idiomatic way by fully qualifying it with std:: as shown below.
template <class T>
void zoo (T t1, T t2) {
//...
int i1, i2;
std::swap(i1,i2); // note uniformity
std::swap(t1,t2); // Ditto here
}
In such a case, the right, efficient implementation of swap is chosen when zoo is instantiated with String class defined earlier. Otherwise, the default std::swap function template would be instantiated -- completely defeating the purpose of defining the member swap and namespace scope swap function. This idiom of defining explicit specialization of swap in std namespace is particularly useful in generic programming.
Such uniformity in using non-throwing swap idiom leads to its cascading use as given in the example below.
class UserDefined
{
String str;
public:
void swap (UserDefined & u) // noexcept
{
std::swap (str, u.str);
}
};
namespace std
{
// Full specializations of the templates in std namespace can be added in std namespace.
template <>
void swap (UserDefined & u1, UserDefined & u2) // noexcept
{
u1.swap (u2);
}
}
class Myclass
{
UserDefined u;
char * name;
public:
void swap (Myclass & m) // noexcept
{
std::swap (u, m.u); // cascading use of the idiom due to uniformity
std::swap (name, m.name); // Ditto here
}
}
namespace std
{
// Full specializations of the templates in std namespace can be added in std namespace.
template <>
void swap (Myclass & m1, Myclass & m2) // noexcept
{
m1.swap (m2);
}
};
Therefore, it is a good idea to define a specialization of std::swap for the types that are amenable to an exception safe, efficient swap implementation. The C++ standard does not currently allow us to add new templates to the std namespace, but it does allow us to specialize templates (e.g. std::swap) from that namespace and add them back in it.
Caveats
[edit | edit source]Using non-throwing swap idiom for template classes (e.g., Matrix<T>) can be a subtle issue. As per the C++98 standard, only the full specialization of std::swap is allowed to be defined inside std namespace for the user-defined types. Partial specializations or function overloading is not allowed by the language. Trying to achieve the similar effect for template classes (e.g., Matrix<T>) results into overloading of std::swap in std namespace, which is technically undefined behavior. This is not necessarily the ideal state of affairs as indicated by some people in a spectacularly long discussion thread on comp.lang.c++.moderated newsgroup.[1] There are two possible solutions, both imperfect, to this issue:
- Standard-compliant solution. Leveraging on Koenig lookup, define an overloaded swap function template in the same namespace as that of the class being swapped. Not all compilers may support this correctly, but this solution is compliant to the standard.[2]
- Fingers-crossed solution. Partially specialize std::swap and ignore the fact that this is technically undefined behavior, hoping that nothing will happen and wait for a fix in the next language standard.
Known Uses
[edit | edit source]All boost smart pointers (e.g., boost::shared_ptr)
Related Idioms
[edit | edit source]References
[edit | edit source]- ↑ "Namespace issues with specialized swap". comp.lang.c++.moderated (Usenet newsgroup). 12 March 2000.
- ↑ Sutter, Herb; Alexandrescu, Andrei (25 October 2004). C++ Coding Standards: 101 Rules, Guidelines, and Best Practices. C++ In-Depth. Addison Wesley Professional. ISBN 0-321-11358-6. Item 66: Don't specialize function templates.