C++ Move Semantics and Rvalues References
Date: 2022-12-30Last modified: 2023-06-27
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.
☕
Information
std::move
does not itself do any moving, but merely converts its
arguments into a so-called rvalue reference, which is a type
declared with two ampersand: X&&
.
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