C++11 - Rvalues & Move Semantics

Lvalues, Rvalues & Xvalues

Till C++98 lvalues were defined as named objects which could appear on the left hand side of an assignment expression and rvalues were those which could appear on the right. Example -
int a = 1; //a is lvalue, 1 is rvalue
char b = '2' = //b is lvalue, '2' is rvalue

Lvalue references can only be bound to lvalues and not rvalues. Example -
int &x = a; //a is lvalue, x is lvalue reference
int &y = 1; //Error, won't compile as 1 is a rvalue

However there is an exception, that allows passing rvalues to functions which are expecting lvalue references. Example -
int string_length(const std::string& x); //x is a const lvalue reference
string_length("whats my length"); //Ok, will compile

C++11 introduced Rvalue references, which can only be bound to rvalues. Example -
int&& p = 1; //p is rvalue reference, declared using &&
int q = 2;
int && r = q; //Error, won't compile, rvalue ref r cannot bind to lvalue q

C++11 also introduced Xvalues, which can be bound to an rvalue reference although its not a tradition rvalue. Example -
int q = 2;
int&& r = std::move(q); //Ok, will compile
int&& s = static_cast<int&&>(q); //same as above
This is useful when you will no longer be using q as an lvalue.

Move Semantics

Rvalues are used to refer to temporaries, where the temporary can no longer be used i.e. referred to, after the execution of statement finishes. Example -

class BigObject
{
  private:
    int x, y;
    int* memory;

  public:
    BigObject(int a, int b): x(a), y(b) {}
    virtual ~BigObject() {}
    const BigObject operator+(const BigObject lhs, const BigObject rhs)
    { return BigObject(lhs.x + rhs.x, lhs.y + rhs.y); }
    void AllocateMemory()
    { memory = new int[x+y]; }
};

BigObject a, b;
BigObject c = a + b;

The statement "a + b" inovkes the operator+() function on class BigObject, creates a temporary object, adds the contents of 'a' and 'b' and assigns them to the temporary object. The return type of operator+() is 'const BigObject'. This temporary is then assigned to 'c'. At this point the compiler optimizes the return type by allocating the memory for the temporary as the memory for 'c'. However, if the code looked like -

BigObject a, b, c;
//... other lines of code
c = a + b;

Since 'c' was declared and initialized, and even possibly assigned to before before the last line was reached, the return value optimization cannot be applied. The temporary object is created as the return of operator+9) and then assigned to 'c' using the operator=().

Since this temporary is no longer used after the end of execution of this line, its best to move (not copy) contents of it over to 'c'. The move assignment operator=() can be written as -

BigObject& operator=(BigObject&& rhs)
{
  if(this != rhs)
  {
    /* clear memory allocated for members of this Big Object
        assign memory for members of rhs to this
        set pointer members of rhs to nullptr */
    delete memory;
    memory = rhs.memory;
    rhs.memory = nullptr;
    x = rhs.x;
    y = rhs.y;
  }
  return * this;
}

Similarly the Move Ctor overloads the Copy Ctor, allowing objects to be built from temporaries. Example -

//Standard Copy Ctor
BigObject(const BigObject& c)
{
  x = c.x;
  y = c.y;
  memory = new int[x+y];
}

//Move Ctor
BigObject(BigObject&& c)
{
  //assign memory held by c to this
  x = c.x;
  y = c.y;
  memory = c.memory;
  c.memory = nullptr; //or alternatively use std::swap
}

BigObject d(a+b); //calls the Move Ctor