Back to TILs

C++ Move Semantics and Rvalues References

Date: 2022-12-30Last modified: 2024-11-02

Table of contents

Introduction

One of the most important new features of C++11 is the support of move semantics. This feature goes further into the major design goal of C++ to avoid unnecessary copies and temporaries.

This new type stands for rvalues (anonymous temporaries that can appear only on the right-hand side of an assignment) that can be modified. The contract is that this is a (temporary) object that is not needed any longer so that you can steal its contents and/or its resources.

  std::string s1{ "Content of s1 string variable" };
  std::string s2{ "Content of s2 string variable" };
  std::string s3{ "Content of s3 string variable" };

  auto printS1S2S3
      = [&]( const std::string &title ) { fmt::print( "\n{}\ns1: {}\ns2: {}\ns3: {}\n", title, s1, s2, s3 ); };

  printS1S2S3( "Initial state" );

  s1 = s2;
  printS1S2S3( "s1 = s2" );

  s1 = std::move( s3 );
  printS1S2S3( "s1 = std::move( s3 )" );

  // equivalent to move assignment
  s1 = static_cast<std::string &&>( s2 );
  printS1S2S3( "s1 = static_cast<std::string&&>( s2 )" );
class DataWithoutMove {
private:
  // a raw pointer to my data
  int        *data;
  std::string trackId;

public:
  DataWithoutMove( int d, std::string tId )
  {
    data    = new int; // heap allocation
    *data   = d;
    trackId = tId;
    fmt::print( "DataWithoutMove Constructor {} {}\n", *data, trackId );
  }

  ~DataWithoutMove()
  {
    if( data != nullptr ) {
      fmt::print( "DataWithoutMove Destructor {} {}\n", *data, trackId );
      delete data;
    }
    else {
      fmt::print( "DataWithoutMove Destructor nullptr\n" );
    }
  }

  // unnecessary copy and inefficiently memory management
  DataWithoutMove( const DataWithoutMove &source ) : DataWithoutMove{ *source.data, "copy-constructor" }
  {
    fmt::print( "DataWithoutMove Copy Constructor {} {}\n", *data, trackId );
  }
};
class DataWithMove {
private:
  // a raw pointer to my data
  int        *data;
  std::string trackId;

public:
  DataWithMove( int d, std::string tId )
  {
    data    = new int; // heap allocation
    *data   = d;
    trackId = tId;
    fmt::print( "DataWithMove Constructor {} {}\n", *data, trackId );
  }

  ~DataWithMove()
  {
    if( data != nullptr ) {
      fmt::print( "DataWithMove Destructor {} {}\n", *data, trackId );
      delete data;
    }
    else {
      fmt::print( "DataWithMove Destructor nullptr\n" );
    }
  }

  DataWithMove( const DataWithMove &source ) : DataWithMove{ *source.data, "copy-constructor" }
  {
    fmt::print( "DataWithMove Copy Constructor {} {}\n", *data, trackId );
  }

  // Move constructor
  DataWithMove( DataWithMove &&source ) : data{ source.data }, trackId{ "move-constructor" }
  {
    // invalidate the pointer on source
    source.data = nullptr;
    fmt::print( "DataWithMove Move constructor {}\n", *data );
  }
};
  {
    fmt::print( "===== SCOPE START =====\n" );
    std::vector<DataWithoutMove> myData;
    fmt::print( "myData.push_back(DataWithoutMove{{ 10, \"push-back\"}});\n" );
    myData.push_back( DataWithoutMove{ 10, "push-back" } );
    fmt::print( "myData.push_back(DataWithoutMove{{ 20, \"push-back\"}});\n" );
    myData.push_back( DataWithoutMove{ 20, "push-back" } );
    fmt::print( "===== SCOPE END =====\n" );
  }
  {
    fmt::print( "===== SCOPE START =====\n" );
    std::vector<DataWithMove> myData;
    fmt::print( "myData.push_back(DataWithMove{{ 10, \"push-back\"}});\n" );
    myData.push_back( DataWithMove{ 10, "push-back" } );
    fmt::print( "myData.push_back(DataWithMove{{ 20, \"push-back\"}});\n" );
    myData.push_back( DataWithMove{ 20, "push-back" } );
    fmt::print( "===== SCOPE END =====\n" );
  }

Possible output


Initial state
s1: Content of s1 string variable
s2: Content of s2 string variable
s3: Content of s3 string variable

s1 = s2
s1: Content of s2 string variable
s2: Content of s2 string variable
s3: Content of s3 string variable

s1 = std::move( s3 )
s1: Content of s3 string variable
s2: Content of s2 string variable
s3: 

s1 = static_cast<std::string&&>( s2 )
s1: Content of s2 string variable
s2: 
s3: 
===== SCOPE START =====
myData.push_back(DataWithoutMove{ 10, "push-back"});
DataWithoutMove Constructor 10 push-back
DataWithoutMove Constructor 10 copy-constructor
DataWithoutMove Copy Constructor 10 copy-constructor
DataWithoutMove Destructor 10 push-back
myData.push_back(DataWithoutMove{ 20, "push-back"});
DataWithoutMove Constructor 20 push-back
DataWithoutMove Constructor 20 copy-constructor
DataWithoutMove Copy Constructor 20 copy-constructor
DataWithoutMove Constructor 10 copy-constructor
DataWithoutMove Copy Constructor 10 copy-constructor
DataWithoutMove Destructor 10 copy-constructor
DataWithoutMove Destructor 20 push-back
===== SCOPE END =====
DataWithoutMove Destructor 10 copy-constructor
DataWithoutMove Destructor 20 copy-constructor
===== SCOPE START =====
myData.push_back(DataWithMove{ 10, "push-back"});
DataWithMove Constructor 10 push-back
DataWithMove Move constructor 10
DataWithMove Destructor nullptr
myData.push_back(DataWithMove{ 20, "push-back"});
DataWithMove Constructor 20 push-back
DataWithMove Move constructor 20
DataWithMove Constructor 10 copy-constructor
DataWithMove Copy Constructor 10 copy-constructor
DataWithMove Destructor 10 move-constructor
DataWithMove Destructor nullptr
===== SCOPE END =====
DataWithMove Destructor 10 copy-constructor
DataWithMove Destructor 20 move-constructor

References