How it works...

In this recipe, we will explore the difference between a move and copy and how this relates to the Big Five, which is a reference to five functions that all classes should explicitly define. To start, let's first look at a simple example of a class that outputs an integer value in its constructor:

class the_answer
{
int m_answer{42};

public:

~the_answer()
{
std::cout << "The answer is: " << m_answer << ' ';
}
};

In the preceding example, the class will output to stdout when the class is destructed. The class also has an integer member variable that is initialized on construction. The problem with the preceding example is that the implicit copy and move semantics are suppressed because we defined the class's destructor.

The Big Five are the following functions, which every class should define if at least one of these functions are defined (that is, if you define one, you must define them all):

~the_answer() = default;

the_answer(the_answer &&) noexcept = default;
the_answer &operator=(the_answer &&) noexcept = default;

the_answer(const the_answer &) = default;
the_answer &operator=(const the_answer &) = default;

As shown, the Big Five includes the destructor, move constructor, move assignment operator, copy constructor, and copy assignment operator. The author of these classes need not implement these functions but instead should—at a minimum—define the functions, explicitly stating how deletions, copying, and moving should take place (if at all). This ensures that if one of these functions is defined, the rest of the class's move, copy, and destruction semantics are correct, as in this example:

class the_answer
{
int m_answer{42};

public:

the_answer()
{
std::cout << "The answer is: " << m_answer << ' ';
}

public:

virtual ~the_answer() = default;

the_answer(the_answer &&) noexcept = default;
the_answer &operator=(the_answer &&) noexcept = default;

the_answer(const the_answer &) = default;
the_answer &operator=(const the_answer &) = default;
};

In the preceding example, the class is marked as virtual by defining a virtual destructor (meaning the class is capable of participating in runtime polymorphism). No implementation is needed (by setting the destructor to default), but the definition itself is explicit, which tells the compiler that we want the class to support virtual functions. This tells the user of the class that a pointer to this class can be used to delete an instance of any class that derives from it. It also tells the user that inheritance will leverage runtime polymorphism and not composition. This class also states that copies and moves are both allowed.

Let's look at another example:

class the_answer
{
int m_answer{42};

public:

the_answer()
{
std::cout << "The answer is: " << m_answer << ' ';
}

public:

~the_answer() = default;

the_answer(the_answer &&) noexcept = default;
the_answer &operator=(the_answer &&) noexcept = default;

the_answer(const the_answer &) = delete;
the_answer &operator=(const the_answer &) = delete;
};

In the preceding example, copies are explicitly deleted (which is the same as defining a move constructor without defining copy semantics). This defines a move-only class, which means that the class can only be moved; it cannot be copied. An example of such a class in the standard library is std::unique_ptr.

The next class implements the opposite:

class the_answer
{
int m_answer{42};

public:

the_answer()
{
std::cout << "The answer is: " << m_answer << ' ';
}

public:

~the_answer() = default;

the_answer(the_answer &&) noexcept = delete;
the_answer &operator=(the_answer &&) noexcept = delete;

the_answer(const the_answer &) = default;
the_answer &operator=(const the_answer &) = default;
};

In the preceding example, we have explicitly defined a copy-only class.

There are many different combinations of the Big Five. The point of this recipe is to show that explicitly defining these five functions ensures that the author of the class is explicit about the intent of the class itself. This is with respect to how it should operate and how a user should use the class. Being explicit ensures the author of the class doesn't intend for one type of behavior, but instead gets another because of how the compiler will implicitly construct the class based on the compiler's implementation and how the C++ specification was defined.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset