In the following snippet, we’ll look at a barebones std::vector implementation, designed as if move semantics did not exist. This will provide a real-world example of the “rule of three“ in use.
Please be aware that the code below:
Does NOT deal with exception safety
Does NOT deal with deferred construction of objects
Does NOT provide the entire std::vector interface
This is just an example focused on resource management and should not be seen as a valid std::vector implementation.
Let’s think about the design of our vector clone. The vector class is both responsible for the management of a dynamically-allocated buffer and a _size that keeps track of how many elements are in the container.
Let’s try to apply the rule of zero and move as much resource management code as possible in a separate class. Luckily, std::unique_ptr is a great fit here.
// using `= default` here forces the compiler to automatically generate // the move operations for us, even though implicitly generation was suppressed // due to the presence of copy operations.
Due to the fact that our copy operations have to take the _size member variable into account, the “rule of zero“ cannot be completely applied in this situation, at least not without significant refactoring.
Being able to = default the move operations however prevents possible mistakes and increases maintainability and readability of the code. Getting rid of the manual memory management prevents potential security-critical pitfalls that we’ve seen in the previous lecture.
Move-awareness
At a last major improvement, let’s make our push_back function move-aware: it will move T instances inside the container instead of moving whenever possible.
// using `= default` here forces the compiler to automatically generate // the move operations for us, even though implicitly generation was suppressed // due to the presence of copy operations.
By using perfect-forwarding, we can avoid code repetition and provide a member function that works for both lvalues and rvalues, moving where possible in order to increase performance and provide support for move-only types.
Note that, a real vector implementation would have used “placement new“ to conditionally construct objects in the buffer instead of invoking the copy/move constructors.
Summary
Learned that the Standard Library provides full move semantics support for most of its containers.
Discussed that it also provides utilities that are defined in terms of move semantics, like std::exchange.
Understand that you should make your classes movable: prefer the rule of zero where possible, otherwise follow the rule of five.