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

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;

  if ( m_data != NULL )
   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);
}

Holder createHolder1(int size)
{
  return move(Holder(size)) ;
}


int main()
{
    //Copy constructor
    Holder h1 = createHolder(1000)  ;
    printf( "main %p\n" , &h1 ) ;
    cout << "Move Copy attempt.\n" << endl ;

    Holder h2 = createHolder1(1000)  ;


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

Argument constructor.1000
0x7ffffcba0
Move copy constructor.1000
Destructor.0
Move Assignment.

Argument constructor.500
0x7ffffcc10
Move assignment operator.500
Destructor.0
main 0x7ffffcc00
End of main.
Destructor.1000
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.

The function  "createHolder1" gives a hint to the compiler
to use the move copy constructor when assigning the statement
at:

Holder h2 = createHolder1(1000)  ;


Holder createHolder1(int size)
{
  return move(Holder(size)) ;
}





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.