Home C++ Introduction Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

Move Semantics


Contents

Move Semantics lets us utilize temporary objects. We need a way of intercepting a temporary object and "steal" it's resources. In order to do that we need to distinguish between a temporary resource and a normal resource.

RValue and LValues

If we can take the address of something then it is a lvalue else it is a rvalue. If it has a name then it is a lvalue. Usually things on the left side of an assignment operator are called lvalues and the ones on the right side are called rvalues.However this is not always the case. We can also have lvalues on the right hand side of a statement. The below program shows some example of this concept.
File: values1.cpp
#include <iostream>


using namespace std ;
//https://stackoverflow.com/questions/15765893/what-is-the-reasoning-behind-the-naming-of-lvalue-and-rvalue
//https://learn.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=msvc-170
//https://stackoverflow.com/questions/17357888/exact-difference-between-rvalue-and-lvalue


template <typename T>
constexpr bool is_lvalue(T&&) {
  return is_lvalue_reference<T>{};
}

//Returns a rvalue
int setValue()
{
    return 6;
}

int global = 100;

int& setGlobal()
{
    return global;
}




int main()
{
    int x1 = 100 ;
    //x1 is lvalue because it has an address
    //100 is a rvalue because it is temporary and does
    //not have a permanent address in memory. It may be stored
    //in a register but after the statement,
    //it's value is lost.

    cout <<  is_lvalue( x1 ) << endl ;
    cout <<  is_lvalue( 100 ) << endl ;
    // 100 = x1 ;
    //This produces a compiler error.
    //values1.cpp: In function ‘int main()’:
    //values1.cpp:20:6: error: lvalue required as left operand of assignment
    //   20 |      100 = x1 ;
    //The "100" does not have an address and is a rvalue and we cannot
    //put it to the left as it makes no sense.
    //The left of an assignment must be a lvalue .

    //setValue() = 100 ;
    //Compiler error as the function does not return a lvalue


    setGlobal() = 100 ;
    //The above is all right because the function
    // "setGlobal" returns a lvalue .

     int x2 = x1 ;
     //Here x2 is a lvalue and x1 is also a lvalue.

    int& x3 = x1 ;
    //x3 is a lvalue. There is a name.
    cout <<  is_lvalue( x3 ) << endl ;


}

RValue Reference

We are familiar with lvalue references . We also have rvalue references. A lvalue reference is initialized with a lvalue variable. However a rvalue reference must be initialized with a rvalue. Every type can have a lvalue and a rvalue. Even a rvalue referemce !

File: ref1.cpp
#include <iostream>


using namespace std ;

//This function determines if something
//is lvalue or rvalue
template <typename T>
constexpr bool is_lvalue(T&&) {
  return std::is_lvalue_reference<T>{};
}



//LVALUE REFERENCE
void function1LValueRef( int& a1 )
{
   cout << "function1:" << a1 << endl ;
}

//RVALUE REFERENCE
void function2RValueRef( int&& a1 )
{
  //Since a1 is a name it is a lvalue at this point inside the
  //function but the function can only take rvalues.
  cout << "Is a1 a lvalue: " << is_lvalue( a1 ) << endl ;
  //a1 = 25 ;
}


int main()
{
   int x1 = 10 ;
   int y1 = 30 ;


   //Need to initialize references
   int&& x2R = 20 ;
   int&& x3R = 30 ;
   int& x3L = x1  ;

   //compiler error
   //A rvalue reference must be initialized
   //int&& x7  ;
   //Can only initialize it with a temporary object
   // int&& x7 = x1  ;

   //can reassign values to r reference
   //x2R is not pointing to x3R but rather it's
   //value is being updated.
   x2R = x3R ;
   cout << "x2R:" << x2R << endl ;
   x2R = 35 ;
   cout << "x2R:" << x2R << endl ;
   //Can initialize l-value reference with r-value
   //reference. Anything that has a name is l-value.
   int& x6R = x2R ;

   //cannot initialize lvalue
   //reference with a constant/rvalue
   //At the point of definition
   //int& x8 = 25 ;
   //can do
   //const int& x1 = 20 ;

   //Can only initialize rvalue reference with
   // rvalues
   //int&& x7 = x3L ;



   function1LValueRef( x1 ) ;
   function2RValueRef(  20 ) ;


   //Allowed
   //
   x2R = x1 ;
   x2R = 40 ;   //reassignment to a r value reference

   cout << "x2R:" << x2R << endl ;
   //Not a reassignment of the lvalue reference.
   //changes the value of x1 and  x3L "points" to x1 .
   x3L = y1 ;

   //x2R's value is being set to x1
   x1 = x2R ;
   cout << "x1:" << x1 << endl ;


   //function 2 is r reference
   //Compiler error r-value reference expects a constant
   //  function2RValueRef(  x1  ) ;

    //ok move converts a l value to a r value
   function2RValueRef( move(x1)  ) ;



    //x2R is a name so it's a l value at this point
   function1LValueRef( x2R ) ;


    cout << "Is x2R a lvalue: " << is_lvalue( x2R ) << endl ;
    cout << "Is 12 a lvalue: " << is_lvalue( 12 ) << endl ;


    function2RValueRef(  move(x2R)   ) ;
    cout << "x2R value: " << x2R << endl ;
    //compiler error even though x2R is rvalue !!!
    //But it has a name so now is lvalue !!
    // function2RValueRef(  x2R   ) ;
    function2RValueRef( 21 ) ;

     int x19 = 5 ;
     cout << x19 << " " << move(x19) << endl ;

   return 0 ;
}

The key takeaway from the above code is that a rvalue reference is
declared as:

  int&& x2R = 20 ;

We can only assign a temporary value to it at this point. The
"int&&" is a type. It's lvalue is "x2R" and it's rvalue from the
above statement is "20" . Similarly in the function declaration:

//RVALUE REFERENCE
void function2RValueRef( int&& a1 )
{
  //Since a1 is a name it is a lvalue at this point inside the
  //function but the function can only take rvalues.
  cout << "Is a1 a lvalue: " << is_lvalue( a1 ) << endl ;
  //a1 = 25 ;
}

When we call this function we need to supply a rvalue. So
we can call it like this:

function2RValueRef(  20 ) ;

However we cannot do:

int&& x2R = 20 ;
function2RValueRef(  x2R   ) ;

Remember "x2R" is a name and therefore a lvalue and we
cannot initialize a rvalue reference to a lvalue. This
is the probably the most confusing part of rvalue references.

The rvalue and lvalue are sort of properties of the object and we
can change a variable from a lvalue to a rvalue with the std function
"move" and by doing this we can call our function.

function2RValueRef(  move(x2R)   ) ;

Usage

So we have the concept of rvalue references and we can assign temporary values to it but what's the advantage of this feature ? The below example shows how we can use rvalue references to take advantage of temporary objects when they are no longer needed.

File: usage1.cpp
#include <iostream>

using namespace std ;

class Holder
{
  public:

    Holder(int size)         // Constructor
    {
      cout << "Argument constructor." << size << endl ;
      printf( "%p\n" , this) ;
      m_data = new int[size];
      m_size = size;
    }

    Holder(const Holder& other)
    {
      cout << "Copy constructor." << m_size <<  endl ;
      m_data = new int[other.m_size];  // (1)
      std::copy(other.m_data, other.m_data + other.m_size, m_data);  // (2)
      m_size = other.m_size;
    }

    Holder(Holder&& other)     // <-- rvalue reference in input
    {
      cout << "Move copy constructor." << other.m_size << endl ;
      m_data = other.m_data;   // (1)
      m_size = other.m_size;
      other.m_data = nullptr;  // (2)
      other.m_size = 0;
    }

    Holder& operator=(const Holder& other)
    {
      cout << "assignment operator" << other.m_size << endl ;
      if(this == &other) return *this;  // (1)
      delete[] m_data;  // (2)
      m_data = new int[other.m_size];
      std::copy(other.m_data, other.m_data + other.m_size, m_data);
      m_size = other.m_size;
      return *this;  // (3)
    }

Holder& operator=(Holder&& other)     // <-- rvalue reference in input
{
  cout << "Move assignment operator." << other.m_size <<  endl ;

  if (this == &other)
   return *this;

  delete[] m_data;         // (1)

  m_data = other.m_data;   // (2)
  m_size = other.m_size;

  other.m_data = nullptr;  // (3)
  other.m_size = 0;

  return *this;
}


    ~Holder()                // Destructor
    {
      cout << "Destructor." << m_size << endl ;
      if ( m_data != NULL )
        delete[] m_data;
    }

  private:
    int*   m_data;
    size_t m_size;
};

Holder createHolder(int size)
{
  return Holder(size);
}


int main()
{
    //Copy constructor
    Holder h1 = createHolder(1000)  ;
    printf( "main %p\n" , &h1 ) ;
    cout << "Before Assignment.\n" << endl ;
    //Move Assignment operator
    h1 = createHolder(500);
    printf( "main %p\n" , &h1 ) ;
    cout << "End of main." << endl ;
    return 0 ;
}
$ rm a.exe ; g++ -fno-elide-constructors usage1.cpp ; ./a.exe
Argument constructor.
0x7ffffcc00
main 0x7ffffcc00
Before Assignment.

Argument constructor.
0x7ffffcc10
Move assignment operator.
Destructor.0
main 0x7ffffcc00
End of main.
Destructor.500

We have a holder class with some additonal methods that take a rvalue
reference as a parameter. One is a constructor and the other is the
assignment operator.

	Holder(const Holder& other)
	{
      cout << "Copy constructor." << endl ;
	  m_data = new int[other.m_size];  // (1)
	  std::copy(other.m_data, other.m_data + other.m_size, m_data);  // (2)
	  m_size = other.m_size;
	}

	Holder(Holder&& other)     // <-- rvalue reference in input
	{
	  cout << "Move copy constructor." << endl ;
	  m_data = other.m_data;   // (1)
	  m_size = other.m_size;
	  other.m_data = nullptr;  // (2)
	  other.m_size = 0;
	}

In the above snippet we see that with a regular copy constructor
we allocate new memory and copy the values from the argument object.
In the rvalue reference constructor we just use the argument's object pointer to
memory and set the argument's object variables to null. The reason is
so that the destructor does not destroy the "other" variables. We have
"stolen" the resources of the temporary object to make our code
more efficient.

Holder createHolder(int size)
{
  return Holder(size);
}


int main()
{
	//Copy constructor
    Holder h1 = createHolder(1000)  ;

Output
Argument constructor.
0x7ffffcc00
main 0x7ffffcc00
Before Assignment.


What should happen in the above snippet, is that the
"createHolder" returns a temporary object that gets assigned
to "h1" forcing our rvalue reference constructor to be called.
We see that the address of the object in

Holder createHolder(int size)
{
  return Holder(size);
}

is:
0x7ffffcc00

However after the "copy constructor" the address is the same:

main 0x7ffffcc00

This is because of compiler optimization. The compiler does not
destroy the initial created object and reuses it for "h1" so
no "copy constructor" gets called. We can prevent optimization
with the "-fno-elide-constructors" argument.

$ rm a.exe ; g++ -fno-elide-constructors usage1.cpp ; ./a.exe
Argument constructor.1000
0x7ffffcc00
main 0x7ffffcc00
Before Assignment.

Argument constructor.500
0x7ffffcc10
Move assignment operator.500
Destructor.0
main 0x7ffffcc00
End of main.
Destructor.500

That did not help as the compiler still did the optimization.
However we notice that:

    h1 = createHolder(500);

did cause our move assignment operator to get called.

Move assignment operator.500
Destructor.0

Once it was done with the assignment, the compiler decided
to destroy the object returned by:

createHolder(500)

Whose "m_size" is now zero as it was reset by the rvalue reference
assignment operator.



File: usage2.cpp
#include <iostream>

using namespace std ;

class Holder
{
  public:

    Holder(int size)         // Constructor
    {
      cout << "Argument constructor." << endl ;
      m_data = new int[size];
      m_size = size;
    }

    Holder(const Holder& other)
    {
      cout << "Copy constructor." << endl ;
      m_data = new int[other.m_size];  // (1)
      std::copy(other.m_data, other.m_data + other.m_size, m_data);  // (2)
      m_size = other.m_size;
    }

    Holder(Holder&& other)     // <-- rvalue reference in input
    {
      cout << "Move copy constructor." << endl ;
      m_data = other.m_data;   // (1)
      m_size = other.m_size;
      other.m_data = nullptr;  // (2)
      other.m_size = 0;
    }

    Holder& operator=(const Holder& other)
    {
      cout << "assignment operator" << endl ;
      if(this == &other) return *this;  // (1)
      delete[] m_data;  // (2)
      m_data = new int[other.m_size];
      std::copy(other.m_data, other.m_data + other.m_size, m_data);
      m_size = other.m_size;
      return *this;  // (3)
    }

Holder& operator=(Holder&& other)     // <-- rvalue reference in input
{
  cout << "Move assignment operator." << endl ;

  if (this == &other)
   return *this;

  delete[] m_data;         // (1)

  m_data = other.m_data;   // (2)
  m_size = other.m_size;

  other.m_data = nullptr;  // (3)
  other.m_size = 0;

  return *this;
}


    ~Holder()                // Destructor
    {
      cout << "Destructor." << endl ;
      if ( m_data != NULL )
        delete[] m_data;
    }

  private:
    int*   m_data;
    size_t m_size;
};

Holder createHolder(int size)
{
  return Holder(size);
}


int main()
{
    //
    Holder h1(1000)  ;
    {
        Holder h2(1000)  ;
        cout << "Before Assignment.\n" << endl ;
        //Assignment operator
        h1 = move( h2 )  ;
    }
    return 0 ;
}

$ g++ -fno-elide-constructors usage2.cpp ; ./a.exe
Argument constructor.
Argument constructor.
Before Assignment.

Move assignment operator.
Destructor.
Destructor.
The above file shows another way we can use rvalue references.

    Holder h1(1000)  ;
    {
		Holder h2(1000)  ;
		cout << "Before Assignment.\n" << endl ;
		//Assignment operator
		h1 = move( h2 )  ;
    }

We know that "h2" is going out of scope and we want to use
it's resources but it is not a rvalue. However with "move"
we change the property to rvalue and "steal" it's
resources.

Perfect Forwarding


File: foward1.cpp
#include <iostream>

using namespace std ;

class Object
{
 public:
  Object() = default;

  void SetName(const string &name) { name_ = move(name); }
  string GetName() const { return name_; }

 private:
  string name_;
};

void UseObject(Object& x1)
{
  cout << "calling UseObject(Object &)" << endl;
}

void UseObject(const Object& x1)
{
  cout << "calling UseObject(const Object &)" << endl;
}

void UseObject(Object&& x1)
{
  cout << "calling UseObject(Object &&)" << endl;
}

template <typename T>
void NotForwardToUseObject(T x)
{
  UseObject(x);
}

template <typename T>
void ForwardToUseObject(T &&x) {
  UseObject(static_cast<T &&>(x));
}

template <typename T>
void PerfectForwardToUseObject(T &&x) {
  UseObject(forward<T>(x));
}


int main()
{
  Object object;
  const Object const_object;
  UseObject(object);
  UseObject(const_object);
  UseObject(move(object));
  cout << "----" << endl;

   NotForwardToUseObject(object);
   NotForwardToUseObject(const_object);
   NotForwardToUseObject(move(object));

  cout << "----" << endl;
  ForwardToUseObject(object);
  ForwardToUseObject(const_object);
  ForwardToUseObject(move(object));
  cout << "----" << endl;

  PerfectForwardToUseObject(object);
  PerfectForwardToUseObject(const_object);
  PerfectForwardToUseObject(move(object));

}

$ rm a.exe ; g++ forward1.cpp ; ./a.exe
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
----
calling UseObject(Object &)
calling UseObject(Object &)
calling UseObject(Object &)
----
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)
----
calling UseObject(Object &)
calling UseObject(const Object &)
calling UseObject(Object &&)

There are certain cases where we call a function that
can take a lvalue or rvalue reference.In the above file
we can see such a function:

void UseObject(Object& x1)
{
  cout << "calling UseObject(Object &)" << endl;
}

void UseObject(const Object& x1)
{
  cout << "calling UseObject(const Object &)" << endl;
}

void UseObject(Object&& x1)
{
  cout << "calling UseObject(Object &&)" << endl;
}

template 
void NotForwardToUseObject(T x)
{
  UseObject(x);
}

We want that the right "UseObject" should be called
when we call a common function such as "NotForwardToUseObject" .
We want the "UseObject" to be called for lvalue or rvalue references.
However we know that as long as we have a name it is a lvalue. To
preserve the concept of lvalue and rvalue we have the concept of
perfect forwarding.

template 
void PerfectForwardToUseObject(T&& x) {
  UseObject(forward(x));
}

The syntax is not very intuitive or logical but we define a
template function and the argument as "T&& x".
Now with the "forward" function we can preserve the lvalue or
rvalue of the argument.