Move Semantics Explained

Get introduced to move semantics and some important rules for handling resources inside a class.

Understanding Move Semantics

Move semantics is a concept introduced in C++11 that is quite hard to grasp, even by experienced programmers. Therefore, we will try to give you an in-depth explanation of how it works, when the compiler utilizes it, and, most importantly, why it is needed. Essentially, the reason C++ even has the concept of move semantics, whereas most other languages don’t, is a result of it being a value-based language, as discussed before. If C++ did not have move semantics built-in, the advantages of value-based semantics would get lost in many cases, and programmers would have to perform one of the following trade-offs:

  • Performing redundant deep-cloning operations with high-performance costs
  • Using pointers for objects like Java does, losing the robustness of value semantics
  • Performing error-prone swapping operations at the cost of readability

We do not want any of these, so let’s have a look at how move semantics help us.

Copy-construction, swap, and move

Before we go into the details of move, we will first explain and illustrate the differences between copy-constructing an object, swapping two objects, and move-constructing an object.

Copy-constructing an object

When copying an object handling a resource, a new resource needs to be allocated, and the resource from the source object needs to be copied so that the two objects are completely separated. Imagine that we have a class, Widget, that references some sort of resource that needs to be allocated for construction. The following code default-constructs a Widget object and then copy-constructs a new instance:

C++ 17
auto a = Widget{};
auto b = a; // Copy-construction

The resource allocations that are carried out are illustrated in the following figure:

Copying an object with resources
Copying an object with resources

The allocation and copying are slow processes, and, in many cases, the source object isn't needed anymore. With move semantics, the compiler detects cases like these where the old object is not tied to a variable and instead performs a move operation.

Swapping two objects

Before move semantics were added in C++11, swapping the content of two objects was a common way to transfer data without allocating and copying. As shown next, objects simply swap their content with each other:

C++ 17
auto a = Widget{};
auto b = Widget{};
std::swap(a, b);

The following figure illustrates the process:

Swaping resources between two objects
Swaping resources between two objects

The std::swap() function is a simple but useful utility used in the copy-and-swap idiom.

Move-constructing an object

When moving an object, the destination object steals the resource straight from the source object, and the source object is reset.

As we can see, it is very similar to swapping, except that the moved-from object does not have to receive the resources from the moved-to object:

C++ 17
auto a = Widget{};
auto b = std::move(a); // Tell the compiler to move the resource into b

The following figure illustrates the process:

Moving resources from one object to another
Moving resources from one object to another

Although the source object is reset, it's still in a valid state. This resetting of the source object is not something that the compiler does automatically for us. Instead, we need to implement the resetting in the move constructor to ensure that the object is in a valid state that can be destroyed or assigned to.

Moving objects only makes sense if the object type owns a resource of some sort (the most common case being heap-allocated memory). If all data is contained within the object, the most efficient way to move an object is to just copy it.
Let’s review the complete implementation of the move constructor in C++ below:

C++
#if _MSC_VER
#pragma warning(disable : 4996)
#endif
#include <algorithm>
#include <cctype>
#include <gtest/gtest.h>
#include <initializer_list>
#include <locale>
#include <vector>
namespace {
class Widget {
public:
Widget(std::string s) // By value
: s_{std::move(s)} {}
// Move-construct
Widget()
{}
private:
std::string s_;
/* … */
};
TEST(MoveSemantics, InitializingClassMembers) {
auto C = Widget{};
auto D = Widget{};
std::swap(C, D);
auto a = Widget{};
auto b = std::move(a); // Tell the compiler to move the resource into b
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}