In C++ it is pretty hard to make exception safe code.
Exceptions can come at basically any time. Every call to
new
can throw, every function call (or
operator function!) can throw. To make good programs we
have to figure out how to make code that won't fail in the
face of exceptions.
The copy and swap idiom addresses this in copy constructors, and helps us to simplify code.
First lets start with a basic example of a class with a controlled pointer:
class SwapExample { int* my_int; public: SwapExample(int i) : my_int(new int(i)) {} int get() const { return *my_int; } void set(int i) { *my_int = i; } ~SwapExample() { delete my_int; } }; int main() { SwapExample se(3); SwapExample copy = se; }
But when this program is ran, there is a double free. This is because we didn't properly follow the rule of three. When you make a destructor that frees a resource, you need to also make a copy constructor and copy assignment operator. Here is an example of this:
class SwapExample { int* my_int; public: SwapExample(int i) : my_int(new int(i)) {} // copy constructor SwapExample(const SwapExample& other) : my_int(new int(*other.my_int)) {} // copy assignment operator SwapExample& operator=(const SwapExample& other) { delete my_int; my_int = new int(*other.my_int); return *this; } int get() const { return *my_int; } void set(int i) { *my_int = i; } ~SwapExample() { delete my_int; } }; #include <assert.h> int main() { SwapExample se(3); SwapExample copy = se; copy.set(5); assert(copy.get() == 5); assert(se.get() == 3); }
This is a decent implementation, but it breaks the DRY
principle of not repeating yourself. We repeat the
delete my_int
in the copy assignment operator
and the destructor, and new int(*other.my_int)
in the copy constructor and the copy assignment operator.
In addition, it is not exception safe because if the
memory allocation fails in the copy constructor,
my_int
points to deallocated memory, but the
user of this class might still use it.
If we instead copy the object, then swap in those new values, we would solve both the repetition problem and the exception unsafety.
class SwapExample { int* my_int; public: SwapExample(int i) : my_int(new int(i)) {} // copy constructor SwapExample(const SwapExample& other) : my_int(new int(*other.my_int)) {} // copy assignment operator SwapExample& operator=(SwapExample copy) { swap(copy); return *this; } void swap(SwapExample& other) { // allow swap to work for built in types using std::swap; swap(my_int, other.my_int); } int get() const { return *my_int; } void set(int i) { *my_int = i; } ~SwapExample() { delete my_int; } }; // custom overload of std::swap namespace std { void swap(SwapExample& first, SwapExample& second) { first.swap(second); } }
By having our copy assignment operator take the class by value, it allows some compiler optimizations and allows brevity in code. The following code has about the same semantic meaning, but has more boilerplate (that I underlined):
SwapExample& operator=(const SwapExample& other) { SwapExample copy(other); swap(copy); return *this; }
In summary, using swap
provides more
functionality for your class (a way to swap instances),
simplifies the copying of the class by removing redundant
code, and is safer.