Modern C++ In-Depth — Move Semantics, Part 2

Phani Adusumilli
FactSet
Published in
5 min readMay 3, 2022

--

In our last post, we discussed the benefits of using std::move(...) to avoid copying large containers. In this installment, we'll take a look at how we might go about defining move semantics for our own custom types, should we need them.

Terminology

Before we get too deep, let’s examine the terms lvalue and rvalue.

The first letter in these two terms derives from the observation that lvalues typically appear on the left-hand side of an assignment operator, whereas rvalues (when thought of as the temporary return values of function calls) tend to appear on the right-hand side. Since these value categories don’t always appear in the context of assignments, it may be more useful to think of an lvalue as a named object instance whose address can be taken. By extension, rvalues don’t have names and their addresses cannot be taken.

Value References

The magic that makes std::move(...) work is a new type of reference, called an rvalue reference (first introduced in C++11). In order to declare an rvalue reference, use two ampersands instead of one.

Here is an example of this new syntax in comparison to the traditional lvalue reference syntax that we’ve seen before:

Widget& get_widget();
Widget make_widget();
Widget& y = get_widget(); //< An lvalue reference.
Widget&& z = make_widget(); //< An rvalue reference.

While a non-const lvalue reference can only be bound to a named object whose address can be taken (i.e., an lvalue), an rvalue reference can only be bound to a temporary (i.e., an rvalue).

Since l- and rvalue references will bind to different value categories (albeit with some overlap), we can now write functions that behave differently depending on the lifetime of the passed-in object. In other words, we can ensure that a function that takes an lvalue reference as input copies heap-allocated resources, whereas the corresponding rvalue reference overload assumes ownership of resources that would otherwise expire.

Implementing Move Semantics

As discussed in an earlier post revisiting the Rule of Three, the compiler should be able to generate the necessary functions to support move semantics automatically for most user-defined classes. In general, one should only have to implement the move-constructor and…

--

--

Phani Adusumilli
FactSet
Editor for

I’m an architect who is passionate about distributed systems, APIs, Data Lakes, and Data Mesh in Financial Services