the Chromium logo

The Chromium Projects

Rvalue references in Chromium

Now that C++11 support is available in our standard library (libc++), we should be able to make use of standard container types in more places, and to use containers and standard types more efficiently. In order to do that we need to understand and make use of std::move() and rvalue references.

We hope that the information here will be enough for you to start using these C++11 features safely in Chromium. (Some of the information here may purposefully gloss over details or edge cases.)

Rvalue references and std::move()

The google style guide allows the following: Use rvalue references only to define move constructors and move assignment operators, or for perfect forwarding. Here we talk about the first type of use, and follow up about the second later.

Many of the ideas here come from a talk given by Scott Meyers that is available on MSDN. The video is really excellent and worth watching.

There are links to full example code with each of the examples. You can try them out locally, or drop them into an online compiler.

1. What makes rvalues.

You know that you have an rvalue when the object has no name. Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation.

MyType MakeMyType() {
  MyType type;
  return type;
}

int main(int argc, char **argv) {
  // MakeMyType returns an rvalue MyType.
  MyType type = MakeMyType();
  ProcessMyType(MakeMyType());
}

http://pastebin.com/raw.php?i=ki3q2Gbi

2. Move constructor is used with rvalues.

Move constructors are similar to copy constructors. They construct an object out of another object of the same type. The signature of the method changes in order for it to accept rvalues only.

Here’s an example of a class with a move constructor. It will cause the compiler to not generate an implicit copy constructor, making this class move-only.

class MyType {
 public:
  MyType() {}
  MyType(MyType&& other) { fprintf(stderr, "move ctor\n"); }
  // Copy constructor is implicitly deleted.
  // MyType(const MyType& other) = delete;
};

MyType MakeMyType(int a) {
  // This is here to circumvent some compiler optimizations,
  // to ensure that we will actually call a move constructor.
  if (a % 2) {
    MyType type;
    return type;
  }
  MyType type;
  return type;
}

int main(int argc, char **argv) {
  // MakeMyType returns an rvalue MyType.
  // Both lines below call our move constructor.
  MyType type = MakeMyType(2);
  ProcessMyType(MakeMyType(2));
}

http://pastebin.com/raw.php?i=tjxdwQuL

The argument of the move constructor is a MyType&& which is a reference (like a MyType&) except that it will only bind to an rvalue. That means the following won’t compile, since it tries to construct a new MyType without an rvalue.

class MyType {
 public:
  MyType() {}
  MyType(MyType&& other) { fprintf(stderr, "move ctor\n"); }
  // Copy constructor is implicitly deleted.
  // MyType(const MyType& other) = delete;
};

int main(int argc, char** argv) {
  MyType type;
  // |type| is an lvalue, so this will not compile because move constructor's
  // argument will only bind to rvalues.
  MyType other_type = type;
}

http://pastebin.com/raw.php?i=9bjPNiWY

3. Move constructor does a shallow copy.

A move constructor (like a copy constructor) works by copying the members of another object into itself. The differences of a move constructor are the following:

class MyType {
 public:
  MyType() {
    pointer_ = new int;
    *pointer_ = 1;
    memset(array_, 0, sizeof(array_));
    vector_.push_back(3.14);
  }

  MyType(MyType&& other) {
    fprintf(stderr, "move ctor\n");

    // Steal the memory, null out |other|.
    pointer_ = other.pointer_;
    other.pointer_ = nullptr;

    // Copy the contents of the array.
    memcpy(array_, other.array_, sizeof(array_));

    // Swap with our (empty) vector.
    vector_.swap(other.vector_);
  }

  ~MyType() {
    delete pointer_;
  }

 private:
  int* pointer_;
  char array_[42];
  std::vector<float> vector_;
};

int main(int argc, char **argv) {
  // MakeMyType returns an rvalue MyType.
  // Both lines below call our move constructor.
  MyType type = MakeMyType(2);
  ProcessMyType(MakeMyType(2));
}

http://pastebin.com/raw.php?i=TXZB61Sg

4. std::move() will cast a variable to an rvalue.

If you want to construct a new object from a variable, you can still use the move constructor to do so. std::move() will cast your variable to an rvalue, allowing it to bind to a move constructor.

int main(int argc, char** argv) {
  MyType type;
  // MyType other_type = type; won't work, since there is no copy ctor.
  // std::move casts |type| to an rvalue, so it uses the move constructor.
  MyType other_type = std::move(type);
}

http://pastebin.com/raw.php?i=DzNMxt5j

This also means that you can pass an rvalue to a function that uses pass-by-value.

int main(int argc, char** argv) {
  MyType type;

  // The return value of MakeMyType is an rvalue, so the move constructor
  // is used.
  ProcessMyType(MakeMyType(2));

  // Error: |type| is an lvalue, and is not copyable.
  ProcessMyType(type);

  // But std::move() turns lvalues into rvalues, so the move constructor
  // is used again.
  ProcessMyType(std::move(type));
}

http://pastebin.com/raw.php?i=P7prZEur

However..

5. You must not use a variable after calling std::move() on it.

Since you have casted your variable to an rvalue, functions that receive rvalues may act destructively on your variable, so using the variable’s contents afterward may result in undefined behaviour. The only valid things you may do after calling std::move() on a variable are:

This applies to member variables too, don't leave your class in an undefined state when leaving a method.

6. std::move() does not move, it casts.

You may think of std::move<T>() as instead rvalue_cast<T>(). It does not perform any action of its own on the value passed to it, only changing what methods you can pass it to.

So the following does nothing:

MyType t;
std::move(t); // Casts it but it’s unused.

Just the same as:

char t;
reinterpret_cast&lt;unsigned char&gt;(t); // Cast it but it’s unused.

In fact, the actual implementation of std::move() is done with static_cast. It is simply a cast from MyType to MyType&& (a reference to an rvalue of MyType):

return static_cast<typename std::remove_reference<T>::type&&>(t);

7. Copy constructors can also bind to rvalues (with lower priority).

Never read std::move() on an unfamiliar type and assume anything about what is going to happen!

If you see code that calls std::move() it looks like it should be calling a move constructor, but you could be wrong.

When a move constructor on the type being cast to an rvalue does not exist, the compiler will fall back to binding the rvalue as a const-reference. This means the copy-constructor would be used instead.

class MyType {
 public:
  MyType() {
    pointer_ = new int;
    *pointer_ = 1;
    memset(array_, 0, sizeof(array_));
    vector_.push_back(3.14);
  }

  MyType(const MyType& other) {
    fprintf(stderr, "copy ctor\n");

    // Copy memory.
    pointer_ = new int;
    *pointer_ = *other.pointer_;

    // Copy the contents of the array.
    memcpy(array_, other.array_, sizeof(array_));

    // More copies.
    vector_ = other.vector_;
  }

  ~MyType() {
    delete pointer_;
  }

 private:
  int* pointer_;
  char array_[42];
  std::vector<float> vector_;
};

int main(int argc, char** argv) {
  {
    MyType type;
    // MyType other_type = type; works the same way here.
    // std::move casts |type| to an rvalue, but since there is no move
    // constructor, the copy constructor is used.
    MyType other_type = std::move(type);
  }

  {
    MyType type;
    // MakeMyType returns an rvalue, but since there is no move constructor, the
    // copy constructor is used.
    ProcessMyType(MakeMyType(2));
    // ProcessMyType(type); works the same way here.
    // std::move casts |type| to an rvalue, but since there is no move
    // constructor, the copy constructor is used.
    ProcessMyType(std::move(type));
  }
}

http://pastebin.com/raw.php?i=p5ciyVVk

So do not assume anything from reading std::move(). You need to understand the class being moved to know what will happen.

8. Move constructors will only bind to non-const rvalues.

Beware of const! Const variables can not be moved from, since move constructors take a non-const (rvalue) reference. However the compiler will silently fall-back to the copy constructor.

int main(int argc, char** argv) {
  const MyType type = MakeMyType(2);
  // std::move casts to an rvalue, but since this is const, it becomes an rvalue
  // to const, which uses the copy constructor, not the move constructor.
  ProcessMyType(std::move(type));
}

http://pastebin.com/raw.php?i=5aVRz5SQ

This type of error can only be caught by code inspection (and performance testing). Always look at the type of the variable on which you call move().

This won’t bite you with function return rvalues, but requires caution when using std::move().

9. Move constructors should be accompanied by move-assignment operators.

Just as a copy constructor MyType(const MyType& t) should be accompanied by a copy-assignment operator operator=(const MyType& t), so should a move constructor.

Move-assignment operators work and look the same as a Move constructor. Most things we’ve discussed about move constructors (behaviour and performance) will apply in all the same ways to move-assignment.

In following example, the class defines its move constructor in terms of operator=, using a move assignment to construct itself.

class MyType {
 public:
  MyType() {
    pointer_ = new int;
    *pointer_ = 1;
    memset(array_, 0, sizeof(array_));
    vector_.push_back(3.14);
  }

  MyType(MyType&& other) : pointer_(nullptr) {
    // Note that although the type of |other| is an rvalue reference, |other|
    // itself is an lvalue, since it is a named object. In order to ensure that
    // the move assignment is used, we have to explicitly specify
    // std::move(other).
    *this = std::move(other);
  }

  ~MyType() {
    delete pointer_;
  }

  MyType& operator=(MyType&& other) {
    // Steal the memory, null out |other|.
    delete pointer_;
    pointer_ = other.pointer_;
    other.pointer_ = nullptr;

    // Copy the contents of the array.
    memcpy(array_, other.array_, sizeof(array_));

    // Swap with our vector. Leaves the old contents in |other|.
    // We could destroy the old content instead, but this is equally
    // valid.
    vector_.swap(other.vector_);

    return *this;
  }

 private:
  int* pointer_;
  char array_[42];
  std::vector<float> vector_;
};

http://pastebin.com/raw.php?i=WMZg2y3C

10. Don’t go cray cray with move semantics.

Move-constructing is always as cheap or cheaper than copy-constructing. So you could use it in every situation possible. But it may be adding very little value (or making code misleading).

Almost always, just use a copy constructor. For example gfx::Point is 2 integers, a move constructor would do nothing different than a copy constructor. There’s no reason to write a move constructor for this class, or to use std::move() when using Points.

class Point {

  int x_;
  int y_;

  Point(const Point& p) { x_ = p.x_; y_ = p.y_; }

  Point(Point&& p) { x_ = p.x_; y_ = p.y_; }
};

Move constructors are especially valuable for types that hold a pointer to something large, since moving a pointer is much cheaper than copying everything it points to. Other scenarios benefit less or not at all.

11. Don’t return references. Don’t use std::move() on rvalues.

Functions return an rvalue without you doing anything, they already do this (see point #1). So you don’t need to do anything to make this happen. You should not see functions that return a MyType&&. Simply return MyType.

MyType&& MakeMyType() { // This is WRONG. The return type should not be

  // a reference.
  MyType type;
  return std::move(type); // This is usually WRONG. Returning will
  // already make it an rvalue.
}

Just as returning a MyType& would be wrong (returning a reference to a temporary object), returning a MyType&& is wrong for the same reasons.

Putting std::move() on the return call actually makes things less efficient, it interferes with the compiler doing RVO (Return Value Optimization). You can read a previous chromium-dev thread about this in the context of scoped_ptr: link.

Similarly do not use move() on the return value from calling a function. You already have an rvalue, so casting it there adds no value, only noise.

// This is WRONG. It is casting an rvalue to an rvalue, which is not
// needed.
ProcessMyType(std::move(MakeMyType()));

12. std::move() is the same thing as our familiar Pass() function.

For some time, scoped_ptr::Pass() has been implemented as being the same thing as std::move().

Places that previously called a.Pass() can be changed to call std::move(a) instead, and they will be changed in the near future. It may help you to consider this, if you are already familiar with Pass().

Using std::unique_ptr instead of scoped_ptr will require the use of std::move() instead, as Pass() does not exist there (and is not needed). So code will be change from:

scoped_ptr<MyType> t = ...;
CallFunction(t.Pass());

To instead be:

std::unique_ptr<MyType> t = ...;
CallFunction(std::move(t));

Perfect forwarding

The google style guide allows the following: Use rvalue references only to define move constructors and move assignment operators, or for perfect forwarding. We’ll talk about the latter here.

So far, we’ve seen that we can write an rvalue reference in a move constructor, or a move-assignment operator:

MyType(MyType&& type); // Move constructor

operator=(MyType&& type); // Move-assignment operator

In both of these cases, the type MyType is fixed, so we know that we have a reference to something that is definitely an rvalue. However when writing a templated method, we may want to support an unknown type instead, at which point we don’t know if it is an rvalue.

template<typename T> 
void ProcessAndStore(T&& var) { ... }

Here the argument type may have been one of two things:

(You can read more about reference collapsing if you want more details about how these are the only two possibilities.)

Arguments in templates can be written this way to implement perfect forwarding. This allows for arguments to be passed through the template to another method without introducing any additional copies of the object (ie no copy or move constructors occur inside the template):

The most generally correct way to implement this is with std::forward(), which will always do the right thing for you.

template<typename T>
void ProcessAndStore(T&& var) {
  // Process
  // ...

  // The type of |var| could be an rvalue reference, which means we should pass
  // an rvalue to Store. However, it could also be an lvalue reference, which
  // means we should pass an lvalue.
  // Note that just doing Store(var); will always pass an lvalue and doing
  // Store(std::move(var)) will always pass an rvalue. Forward does the right
  // thing by casting to rvalue only if var is an rvalue reference.
  Store(std::forward<T>(var));
}

http://pastebin.com/raw.php?i=rVRebELt

std::forward() is a conditional cast to an rvalue.

This sounds lot like std::move(), which also casts to an rvalue. But std::forward() will avoid casting to an rvalue if the argument is an lvalue reference. It’s implementation is conceptually something like:

if (is_lvalue_reference<T>::value) {
  return t;
}
return std::move(t);

That is, it preserves lvalue references. Why?

Because doing std::move() on an lvalue reference is bad! Code producing an lvalue reference expects the object to remain valid. But code receiving an rvalue reference expects to be able to steal from it. This leaves you with a reference pointing to a potentially-invalid object.

// Although Chromium disallows non-const lvalue references, this situation is
// still possible in templates.
void Oops(MyType& type) {
  // Move is destructive. It can change |type| which is an lvalue reference.
  MyType local_type = std::move(type);

  // Work with local_type.
  // ...
}

int main(int argc, char **argv) {
  MyType type;
  // Don't move type, just pass it.
  Oops(type);
  // Oops, this is a null pointer dereference.
  *type.pointer = 314;
}

http://pastebin.com/raw.php?i=cPrYgGhr

For perfect forwarding in templates (ie arguments of type T&&), use std::forward() instead of std::move().