In C++, a correct resource manager class needs to follow the rule of three. This rule states that if a class defines one of the three special functions (copy constructor, copy assignment, and destructor), it should explicitly define all these three functions.
Of these three functions, the copy assignment function is a bit nuanced. If that function fails, it may leave the original object in an inconsistent state, thus violating strong exception safety.
The copy-and-swap idiom lets us implement the copy assignment operator with a strong exception safety guarantee.
Let’s consider the implementation of a simple storage class. The class implements the constructor, copy constructor, copy assignment operator, and destructor.
#include <algorithm>#include <memory>#include <iostream>class CharStore {public:CharStore(std::size_t size = 0):sz(size),store(std::make_unique<char[]>(sz)) {}// Copy-constructorCharStore(const CharStore& other):sz(other.sz),store(std::make_unique<char[]>(sz)){std::copy(other.store.get(), other.store.get() + sz, store.get());}// Copy-assignmentCharStore& operator=(const CharStore& other) {// 1. prevent self-assignmentif (this != &other) {store.reset(); // 2. delete old data// 3.0 prepare to assign new datasz = other.sz;// 3.1 create storage for assignmentstore = std::make_unique<char[]>(sz);// 3.2 assign new datastd::copy(other.store.get(), other.store.get() + sz, store.get());}return *this;}void copyStr(char const *p, size_t len) {auto dst = store.get();for (size_t i = 0; i < len; ++i) {dst[i] = p[i];}}void print() {auto arr = store.get();for (size_t i = 0; i < sz; ++i)std::cout << arr[i];std::cout << std::endl;}~CharStore() {}private:std::size_t sz;std::unique_ptr<char[]> store;};int main(){CharStore src(14);CharStore dst(14);char const *a = "This is test";char const *b = "That is test";src.copyStr(a, 13);dst.copyStr(b, 13);src.print();dst.print();dst = src;dst.print();}
CharStore
class. This toy class is responsible for storing some ASCII-text in its private variable store
on line 50.CharStore
class from another existing object. Here, we copy the contents of the existing object’s store variable to the store of the new object.CharStore
class to another object of the same class.Note: This class does not provide strong exception safety. This is because if the statement at Line 24 fails, the existing object is modified. Another issue with this implementation is the repetition of the code between the copy constructor and the copy assignment operator.
The copy-and-swap idiom provides an elegant technique to avoid these problems. It utilizes the copy constructor to create a temporary object, and exchanges its contents with itself using a non-throwing swap. Therefore, it swaps the old data with new data. The temporary object is then destructed automatically (RAII).
The following example illustrates the use of the copy-and-swap idiom to correctly implement the copy assignment operator to provide strong exception safety.
#include <algorithm>#include <memory>#include <iostream>class CharStore {public:CharStore(std::size_t size = 0):sz(size),store(std::make_unique<char[]>(sz)) {}// Copy-constructorCharStore(const CharStore& other):sz(other.sz),store(std::make_unique<char[]>(sz)){std::copy(other.store.get(), other.store.get() + sz, store.get());}// Copy-assignmentCharStore& operator=(const CharStore& other) {CharStore temp(other);temp.swap(*this);return *this;}// Non-throwing swap functionvoid swap(CharStore& second) {using std::swap;swap(sz, second.sz);swap(store, second.store);}void copyStr(char const *p, size_t len) {auto dst = store.get();for (size_t i = 0; i < len; ++i) {dst[i] = p[i];}}void print() {auto arr = store.get();for (size_t i = 0; i < sz; ++i)std::cout << arr[i];std::cout << std::endl;}~CharStore() {}private:std::size_t sz;std::unique_ptr<char[]> store;};int main(){CharStore src(14);CharStore dst(14);char const *a = "This is test";char const *b = "That is test";src.copyStr(a, 13);dst.copyStr(b, 13);src.print();dst.print();dst = src;dst.print();}
Here, we modify the copy assignment operator as seen in lines 17–22.
temp
object from the assigned object using the copy constructor.temp
object is created successfully, we swap its content with the existing object using a non-throwing swap.This example shows that the copy-and-swap idiom lets ys avoid code duplication in the copy-assignment operator by reusing the copy-constructor to create the temporary object.
Also, the state of the current object is not modified if the creation of the temporary object fails. The swap happens only after the temporary object is ready to use. This provides strong exception safety for the class above.
The copy-and-swap idiom is an age-old technique used to implement the tricky copy-assignment operator. The benefits of strong exception safety are often useful, and the copy-and-swap idiom allows us to provide such a safety guarantee.