Skip to content

R-value references and move semantics

December 8, 2011

R-value references and move semantics are probably one of the biggest changes to the language—not because much of the language has been changed, but because it will probably completely change our idea of what it means to write efficient code. No longer is returning a large structure from a function, like a vector, inefficient; it becomes, in fact, the preferred way to do things.

R-values and l-values

Firsty, a definition of r-values and l-values. Basically, an r-value is something that can only appear on the right-hand side of an assignment, and an l-value is something that can appear on the left-hand side. In the following code:

int a = 5;
int b = 6;

int c = a * b;
a = 4;
b = a;

X x = X();

The variables a, b, and c are l-values, and the expressions a * b and X() are r-values.

R-value references

First, consider the following code:

class Foo
{
  public:
  Foo(const std::string& s_in)
  : s(s_in) {}

  private:
  std::string s;
};

void
bar()
{
  std::string s("hello world");
  Foo f(s.substr(0, 5));
}

In the call to Foo‘s constructor, we create a temporary string, which is a substring of "hello world", then copy that into the object. The temporary string is then thrown away immediately. So we have in fact made two copies of the string, and thrown one away. What we would like, is to know when something is a temporary, so that we can steal its memory and save the expensive copy. Currently there is no way to do that, we can’t modify the const reference, and we can’t make it a non-const reference because temporaries only bind to const references. This is where r-value references come in.

An r-value reference can only ever bind to r-values, ie. intermediate temporary results of computations. When you accept an r-value reference object, you know that the parameter is a temporary, and since it is about to be destructed anyway, can therefore be modified.

An r-value reference is written with &&, in the above code, the constructor for Foo becomes Foo(std::string&& s_in). There is one more problem however, we can’t just construct s from s_in and expect it to use the r-value constructor. The last rule for r-values is that anything named cannot be an r-value. This is where std::move comes in.

std::move

When you have a named object, and you really do want to treat it as an r-value and allow its memory to be stolen, then use std::move. The constructor above becomes:

Foo(std::string&& s_in)
: s(std::move(s_in)) {}

and now rather than creating the string twice, it is only created once, and its memory is simply moved around.

Perfect forwarding

Firstly, it might be desirable for the parameter to a function to be exactly the type of object that was passed it, regardless of constness or type of reference. The function could take any type of argument, so clearly we need a template parameter, but not just any sort of template parameter. Before r-value references, this type of parameter was actually impossible.

Consider the following function definitions:

template <typename T>
void
a(T t);

template <typename T>
void
b(T& t);

template <typename T>
void
c(const T& t);

a is no good because it will copy everything passed to it, b is no good because it doesn’t work for r-values, and c is no good because it forces everything to be const. The solution is to use an r-value reference template parameter:

template <typename T>
void
foo(T&& t);

This does exactly what we want, it preserves the type of whatever we pass it exactly as it was passed. The reason this works is because of the rules for collapsing &‘s.

If you have a type A, and you pass an A&, then T is A&, and the parameter is A&& & which reduces to A&. If you pass an A&& then T is A&& and the parameter is A&& && which reduces to A&&. The same holds for const references.

This is important when it comes to perfect forwarding, passing parameters to another function whilst perfectly forwarding their types. Suppose that you have several definitions of a function g, and you have a variadic template function f which should perfectly forward some of its arguments to g, you want to forward the arguments perfectly so that the appropriate definition of g is chosen for arity and types:

template <typename A, typename B, typename... Args>
void
f(A&& a, B&& b, Args&&... args)
{
  //do some stuff with a and b
  g(std::forward<Args>(args)...);
}

g will now receive its arguments exactly as they were passed to f. The trick with std::forward is that the template parameter must be supplied. std::forward returns an r-value reference to its parameter, since the parameter can be a reference of & or &&, the reference collapsing rules apply here too, and the argument is therefore perfectly forwarded.

Notice that this is the construct that I used in Variadic templates, part 2 and said that I would explain later. Well here’s the explanation. Since ... is written outside of the call to std::forward, every argument is expanded with a call to std::forward around it, thus perfectly forwarding every single argument in the parameter pack.

Returning objects by value

So then why does returning objects by value become the preferred way to write things? No longer do you have to pass a reference parameter because you know that it will be more efficient, you can do the more natural thing and simply return objects. As long as the object has an r-value constructor, it will be just as efficient to return it, potentially more so because you might have saved a default construction which was unnecessary.

Just remember that anything that is named cannot be an r-value. So when returning named objects, you should use std::move.

Advertisements

From → Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: