Modern C++ Features – Move Semantics

Contents

One of the biggest changes in C++11 were the introduction of rvalue references to allow the implementation of move semantics. 

Like in all of my “New Features” posts, I won’t go into the technical details and dusty corners of the feature. Instead, I will give an overview of the basic idea of move semantics, its related features and how to use them. That alone is enough for more than one post, so I’ll have to split it up.

Data flow in functions

When you observe how data flows in and out of functions, there are a few different general patterns:

Refer to external data: The function works with data that resides outside the function. It either manipulates the data, or it just analyzes it. Depending on that, we usually use non-const or const references to the data.

Create new data:  Some functions create new data, they act as data sources. Often they return that newly created data with their return values, either directly or by returning a pointer to that data. Sometimes they store the newly created data in an object passed into the function by reference or pointer, such as the this-pointer of a member function or a normal non-const reference.

Consume data: Other functions are data sinks. They take the data passed to them and transform it or do something else with it that does not necessarily preserve the data. Often the data consumed by those functions is specifically created and not needed elsewhere.

The move problem

When passing data into a data sink, there are often two objects involved: The original object outside the sink, and a copy of it inside the sink. The same appears with data sources: There is an original object created inside the source function, and the return value is a copy of it is created outside the source.

There are sometimes ways to get around that fact, e.g. there are return value optimizations that create the original object of a data source already outside the function. But not all of these cases can be optimized away or it would require clumsy techniques to do so that obfuscate the code.

In both cases – sinks and sources – the original is no longer needed once the second object is made. So, if we have an object that is not cheap to copy, e.g. because it contains allocated memory or other resources, we’d rather move the contents from the original to the second object than create an exact copy only to throw away the original immediately after.

The solution: rvalue references

The C++11 standard has introduced a new kind of reference, called rvalue references. Broadly simplified, rvalues are temporary values, and rvalue references bind only to temporary objects. Let’s have a look at a small example:

string source() {
  return "Have you seen my bear Tibbers?";
}

void sink(string&& rvalueString) {
  rvalueString.append("\n");
  std::cout << rvalueString;
}

int main() {
  sink(source()); //OK
  
  auto s = source();
  sink(s); //ERROR: s is not an rvalue
}

The first call of `sink` gets passed the return value of the call to `source`, which is a temporary value. So the rvalue reference can bind to it and sink can work with it. As you see, `rvalueString` is not const, so you can modify it. That is perfectly OK, since it is a temporary value that isn’t needed anywhere else.

std::move

The second call to `sink` however does not go so smoothly. The string `s` we pass to the sink is not a temporary object, so it may be needed later on. Therefore the compiler has to refuse to pass the object to a function that assumes it won’t be needed anymore and therefore can modify it at will.

So what can we do to tell the compiler that we do not need `s` any more and that indeed we want it to be consumed by `sink`? Enters `std::move`:

int main() {
  sink(source()); //OK
  
  auto s = source();
  sink(std::move(s)); //OK now
}

`std::move` is a function template which is basically a cast-to-rvalue-reference for whatever you pass to it.

Passing a copy to the sink

But what if we want to use `s` later on and pass it to the sink as well? The obvious solution would be to explicitly create a copy:

int main() {
  auto s = source();
  sink(string{s}); //creates a temporary copy

  //do something with s...
}

But this has several drawbacks, e.g. we’d have to explicitly create a `string` from `s` – and it would not go well if we later chose to change the types. Our use of `auto` would have lost part of its benefit.

At this point I’ll take a break for a shameless cliffhanger – I’ll present the solution on how to provide a better implementation for data sinks in the next posts, together with the concepts of move constructors and move assignment operators.

Previous Post
Next Post

Leave a Reply

Your email address will not be published. Required fields are marked *