One of the most fundamental additions to C++11 is r-value move semantics.
An r-value, in case you had forgotten, is, in simple terms, a variable, typically created by the compiler itself, that doesn’t have a programmer assigned name. r-values can be created as intermediate results while evaluating expressions (such as in x = a + b + c;), return values of functions, and when objects are implicitly constructed to be passed as function arguments (such as a const char * string being implicitly used to construct a std::string object that is passed to a function taking a const std::string & parameter).
I wanted to explore how these r-value moves worked, so I knocked up some code.
The Code
Here’s the code I wrote to test the r-value functionality. It’s all here. If you cut out my commentry you should end up with a runnable program.
Firstly I needed to include some basic headers:
#include <string>
#include <iostream>
#include <algorithm>
using std::cout;
A class for storing a name
I wanted to have a class that I could move about and I want to give that a name. It seemed simpler to include the class’s name in a separate class rather than directly in the main class, so I got:
class mover_name
{
private:
std::string given_name;
std::string creator_name;
public:
mover_name() {}
explicit mover_name( const char * p_name )
: given_name( p_name ) {}
mover_name( const mover_name & r_name )
: creator_name( r_name.given_name ) {}
mover_name( mover_name && r_name )
: creator_name( r_name.given_name ) {}
std::ostream & print( std::ostream & os ) const
{
if( ! given_name.empty() )
os << given_name;
else if( ! creator_name.empty() )
os << "created by " << creator_name;
else
os << (void *)((int)this & 0xffff);
return os;
}
};
std::ostream & operator << (
std::ostream & os,
const mover_name & r_mover_name )
{
return r_mover_name.print( os );
}
A class for moving about
Now I needed a class that I can move around and copy. It consists mainly of basic constructors and similar operators that print a simple message when they are invoked. Note that I’ve added no throw specifications to the move constructors and assignment operator (even though it’s a bit of a lie in this case). This seems to be good practice as I hope to explore in future:
class mover
{
private:
mover_name name;
public:
mover()
{
cout << "default construct [" << name << "]\n";
}
explicit mover( const char * p_name ) : name( p_name )
{
cout << "named default construct [" << name << "]\n";
}
mover( const mover & r_rhs ) : name( r_rhs.name )
{
cout << "copy construct [" << name << "]\n";
}
mover & operator = ( const mover & r_rhs )
{
cout << "[" << name << "]" <<
" copy assigned from [" << r_rhs.name << "]\n";
return *this;
}
mover( mover && r_rhs ) throw() : name( std::move( r_rhs.name ) )
{
cout << "move construct [" << name << "]\n";
}
mover & operator = ( mover && r_rhs ) throw()
{
cout << "[" << name << "]" <<
" move assigned from [" << r_rhs.name << "]\n";
return *this;
}
mover operator + ( const mover & r_rhs )
{
cout << "mover operator +\n";
return mover( "added" );
}
~mover()
{
cout << "destruct [" << name << "]\n";
}
std::ostream & print_name( std::ostream & os ) const
{
os << name;
return os;
}
};
std::ostream & operator << ( std::ostream & os, const mover & r_mover )
{
return r_mover.print_name( os );
}
Non-r-value ref functions
Having got a class to move about, I needed some functions to move it to/from. But first it seemed reasonable to have a set of functions that did not have move semantics, and so illustrated behaviour without the new semantics:
void function_no_rref( const mover & r )
{
cout << "In function_no_rref( const mover & r )"
" [" << r << "]\n";
}
void function_no_rref( mover & r )
{
cout << "In function_no_rref( mover & r )"
" [" << r << "]\n";
}
// The following is intentionally not implemented
//void function_no_rref( mover && r )
//{
// cout << "In function_no_rref( mover && r ) [" << r << "]\n";
//}
Functions with r-value ref params
Now we start getting to the main part of the experiment. I defined a set of functions that take different types of reference to the class defined above:
void function( const mover & r )
{
cout << "In function( const mover & r ) [" << r << "]\n";
}
void function( mover & r )
{
cout << "In function( mover & r ) [" << r << "]\n";
}
void function( mover && r )
{
cout << "In function( mover && r ) [" << r << "]\n";
}
Functions with POD types
Classes are not the only thing that can be moved using C++11, so it seemed reasonable to explore how a basic POD type like int behaved:
void function( const int & r )
{
cout << "In function( const int & r )\n";
}
void function( int & r )
{
cout << "In function( int & r )\n";
}
void function( int && r )
{
cout << "In function( int && r )\n";
}
Functions with string params
Simple const char * types (i.e. quoted C strings) are often implicitly converted to std::string objects in C++ so it seemed worth looking at that also:
void function( const std::string & r )
{
cout << "In function( const std::string & r )\n";
}
void function( std::string & r )
{
cout << "In function( std::string & r )\n";
}
void function( std::string && r )
{
cout << "In function( std::string && r )\n";
}
Calls within functions
That covers the basic calling situation. I next wanted to explore what happens within a function that has been passed various types of reference. Hence I defined a set of functions that include a basic call to one of the functions defined above:
void function_with_call( const mover & r )
{
cout << "In function_with_call( const mover & r )"
" [" << r << "]\n";
function( r );
}
void function_with_call( mover & r )
{
cout << "In function_with_call( mover & r )"
" [" << r << "]\n";
function( r );
}
void function_with_call( mover && r )
{
cout << "In function_with_call( mover && r )"
" [" << r << "]\n";
function( r );
}
Calls within functions using std::move
Obviously a call to a function can also use std::move, so I wanted to see how the behaviour of the previously defined functions changed if std::move was wrapped around the variable in the function call:
void function_with_call_using_move( const mover & r )
{
cout << "In function_with_call_using_move( const mover & r )"
" [" << r << "]\n";
function( std::move( r ) );
}
void function_with_call_using_move( mover & r )
{
cout << "In function_with_call_using_move( mover & r )"
" [" << r << "]\n";
function( std::move( r ) );
}
void function_with_call_using_move( mover && r )
{
cout << "In function_with_call_using_move( mover && r )"
" [" << r << "]\n";
function( std::move( r ) );
}
That’s pretty much all the function calling conventions covered.
Functions for testing return operation
One of the main benefits of r-value moves is the ability to return values (particularly things like std::string) using a move rather than a copy, so I wanted to explore that:
mover function_return_direct()
{
cout << "In function_return_direct()\n";
return mover( "result" );
}
mover function_return_variable()
{
cout << "In function_return_variable()\n";
mover mover_return( "result" );
return mover_return;
}
Code to run the tests
That’s all the functions I needed. Now to exercise the code. As I wanted the name of the function I was calling to be printed out followed by the result of the call I decided to cheat and define a suitable macro:
#define DO( x ) header( #x ); x
void header( const std::string & r_cmd )
{
cout << "\n" << r_cmd << "\n";
std::for_each( r_cmd.begin(), r_cmd.end(),
[](char){ cout << "-"; } );
cout << "\n";
}
Finally to the main() function that called the code:
int main( int argc, char * argv[] )
{
DO( mover mover_a( "A" ); );
DO( mover mover_b( "B" ); );
DO( mover mover_c( mover_a ); );
DO( mover mover_d( std::move( mover_b ) ); );
DO( mover_c = mover_b; );
DO( mover_c = std::move( mover_a ); );
DO( mover_c = mover_a + mover_b; );
DO( mover_c = mover_a + mover( "temp" ); );
DO( mover mover_e( mover( "temp2" ) ); );
DO( function_no_rref( mover_a ); );
DO( function_no_rref( mover( "temp func" ) ); );
DO( function_no_rref( mover_a + mover_b ); );
DO( function_no_rref( std::move( mover_a ) ); );
DO( function( mover_a ); );
DO( function( std::move( mover_a ) ); );
DO( function( mover( "temp func" ) ); );
DO( function( mover_a + mover_b ); );
DO( function_with_call( mover_a ); );
DO( function_with_call( std::move( mover_a ) ); );
DO( function_with_call( mover( "temp func" ) ); );
DO( function_with_call( mover_a + mover_b ); );
DO( function_with_call_using_move( mover_a ); );
DO( function_with_call_using_move( std::move( mover_a ) ); );
DO( function_with_call_using_move( mover( "temp func" ) ); );
DO( function_with_call_using_move( mover_a + mover_b ); );
DO( mover returned( function_return_direct() ); );
DO( mover_a = function_return_direct(); );
DO( mover returned2( function_return_variable() ); );
DO( mover_a = function_return_variable(); );
mover & lref_mover = mover_a;
DO( function_with_call( lref_mover ); );
// mover && rref_mover = mover_a; // Can't bind lval to r-ref
mover && rref_mover = std::move( mover_a );
DO( function_with_call( rref_mover ); );
DO( function_with_call( std::move( rref_mover ) ); );
int i = 0;
DO( function( i ); );
const int ci = 0;
DO( function( ci ); );
DO( function( 1 ); );
DO( function( int() ); );
DO( function( i + i ); );
DO( function( i + ci ); );
DO( function( i + int(ci) ); );
DO( function( int(i + ci) ); );
DO( function( "Hello" ); );
cout << "\ndone\n----\n";
return 0;
}
The results
And here’s the results.
Construction results
Firstly we have two objects to push around the place:
mover mover_a( "A" );
---------------------
named default construct [A]
mover mover_b( "B" );
---------------------
named default construct [B]
Now exploring some basic construction operations. Generally I think there are few surprises here:
mover mover_c( mover_a );
-------------------------
copy construct [created by A]
mover mover_d( std::move( mover_b ) );
--------------------------------------
move construct [created by B]
Let’s try the assignment operator:
mover_c = mover_b;
------------------
[created by A] copy assigned from [B]
mover_c = std::move( mover_a );
-------------------------------
[created by A] move assigned from [A]
Now to create a temporary object using operator +. Here the temporary is move assigned (operator = (mover &&)) to the target variable. It suggests that, similar to the rule that if you define a copy constructor you should also define a copy operator, if you define a move constructor you should also define a move operator. (Note that the results below, the line “named default construct [added]” is generated by the temporary object created as the return value of the operator + method):
mover_c = mover_a + mover_b;
----------------------------
mover operator +
named default construct [added]
[created by A] move assigned from [added]
destruct [added]
Just to check whether the behaviour changes if we force the use of a temporary object in the addition:
mover_c = mover_a + mover( "temp" );
------------------------------------
named default construct [temp]
mover operator +
named default construct [added]
[created by A] move assigned from [added]
destruct [added]
destruct [temp]
This time we create a temporary object and then use that to construct another object. You might expect that the move assignment operator is called to move the temporary object to the named object. However, in this case the compiler is smart enough to avoid creating the intermediate temporary object and creates the target object directly. Hence you should avoid having side-effects in your move operators as in some cases the compiler can avoid move operations entirely:
mover mover_e( mover( "temp2" ) );
----------------------------------
named default construct [temp2]
That’s the constructor operations explored. Now let’s explore which types of functions are called.
Calls without r-value refs
Firstly, looking at calls to a set of functions that don’t have r-value references. These should act in the same way as pre-C++11 calls:
function_no_rref( mover_a );
----------------------------
In function_no_rref( mover & r ) [A]
function_no_rref( mover( "temp func" ) );
-----------------------------------------
named default construct [temp func]
In function_no_rref( const mover & r ) [temp func]
destruct [temp func]
function_no_rref( mover_a + mover_b );
--------------------------------------
mover operator +
named default construct [added]
In function_no_rref( const mover & r ) [added]
destruct [added]
Interestingly, you can std::move to a function that does not have a r-value ref value, e.g.:
function_no_rref( std::move( mover_a ) );
-----------------------------------------
In function_no_rref( const mover & r ) [A]
Basic function calls with r-value refs
Now to introduce a set of functions that does have an r-value ref parameter.
A non-const, named variable will go to a non-const regular ref function:
function( mover_a );
--------------------
In function( mover & r ) [A]
Temporary types will go to the r-value ref variant:
function( std::move( mover_a ) );
---------------------------------
In function( mover && r ) [A]
function( mover( "temp func" ) );
---------------------------------
named default construct [temp func]
In function( mover && r ) [temp func]
destruct [temp func]
function( mover_a + mover_b );
------------------------------
mover operator +
named default construct [added]
In function( mover && r ) [added]
destruct [added]
Calls within a function
Now let’s add a function call _within_ the function we initially call and see where we end up. In the first case we end up where we expect. However, when we call a function with an r-value reference as a parameter, the function that is called inside the function is a regular reference. In other words, whether a function’s parameter is a regular reference or r-value reference doesn’t affect the type of function that is called within the function:
function_with_call( mover_a );
------------------------------
In function_with_call( mover & r ) [A]
In function( mover & r ) [A]
function_with_call( std::move( mover_a ) );
-------------------------------------------
In function_with_call( mover && r ) [A]
In function( mover & r ) [A]
function_with_call( mover( "temp func" ) );
-------------------------------------------
named default construct [temp func]
In function_with_call( mover && r ) [temp func]
In function( mover & r ) [temp func]
destruct [temp func]
function_with_call( mover_a + mover_b );
----------------------------------------
mover operator +
named default construct [added]
In function_with_call( mover && r ) [added]
In function( mover & r ) [added]
destruct [added]
Calls with std::move within a function
So let’s use std::move on the parameter when the inner function is called. In each case we get to the function with the r-value ref:
function_with_call_using_move( mover_a );
-----------------------------------------
In function_with_call_using_move( mover & r ) [A]
In function( mover && r ) [A]
function_with_call_using_move( std::move( mover_a ) );
------------------------------------------------------
In function_with_call_using_move( mover && r ) [A]
In function( mover && r ) [A]
function_with_call_using_move( mover( "temp func" ) );
------------------------------------------------------
named default construct [temp func]
In function_with_call_using_move( mover && r ) [temp func]
In function( mover && r ) [temp func]
destruct [temp func]
function_with_call_using_move( mover_a + mover_b );
---------------------------------------------------
mover operator +
named default construct [added]
In function_with_call_using_move( mover && r ) [added]
In function( mover && r ) [added]
destruct [added]
Results of function return tests
Now let’s explore returning values from a function. Note that the prototype for the function that is called is “mover function_return_direct()”, NOT “mover && function_return_direct()”. Doing the latter is just as wrong as doing “mover & function_return_direct()” (i.e. you’re passing a reference of something that may have already been destroyed).
In this first example the compiler optimizes out any copy or move operations, and constructs the return object in-situ. Once again, make sure ‘move’ does what it says on the tin and no more because it may be optimized out:
mover returned( function_return_direct() );
-------------------------------------------
In function_return_direct()
named default construct [result]
When we assign the result of a function to a pre-existing variable, then the move semantics come into play:
mover_a = function_return_direct();
-----------------------------------
In function_return_direct()
named default construct [result]
[A] move assigned from [result]
destruct [result]
We see the same pattern if the called function returns a named variable from within the function:
mover returned2( function_return_variable() );
----------------------------------------------
In function_return_variable()
named default construct [result]
mover_a = function_return_variable();
-------------------------------------
In function_return_variable()
named default construct [result]
[A] move assigned from [result]
destruct [result]
Exploring the boundaries
The above has covered the typical cases that you’ll use in code. I wanted to play around a bit more to get a more depth feel for how things work. For example, what happens if you create variables of the form “mover &” and “mover &&”?
Calling a function with a variable of type “mover &” calls the regular reference function as you’d expect:
function_with_call( lref_mover );
---------------------------------
In function_with_call( mover & r ) [A]
In function( mover & r ) [A]
However, calling a function with a “mover &&” type _also_ calls the regular reference function and not the r-value ref version. This ties in with the “function_with_call()” experiments. A named variable that is a r-value ref is treated as a regular ref for deciding which function is called:
function_with_call( rref_mover );
---------------------------------
In function_with_call( mover & r ) [A]
In function( mover & r ) [A]
You have to use std::move (or similar) to make a named r-value ref variable call an r-value ref signatured version of a function:
function_with_call( std::move( rref_mover ) );
----------------------------------------------
In function_with_call( mover && r ) [A]
In function( mover & r ) [A]
Results of PODs and r-value refs
Programmer created types are not the only ones that can make use of move semantics. Here “i” is a non-const int and “ci” is a const int:
function( i );
--------------
In function( int & r )
function( ci );
---------------
In function( const int & r )
The following create temporary, unnamed variables, hence use r-value references:
function( 1 );
--------------
In function( int && r )
function( int() );
------------------
In function( int && r )
function( i + i );
------------------
In function( int && r )
Interestingly here, even though an unnamed temporary is created, the const-ness of the ci variable seems to prevent the r-value ref version of the function being called. I wonder if this is a bug in the compiler or C++11 spec? Maybe it’s too esoteric a corner case to worry about though:
function( i + ci );
-------------------
In function( const int & r )
function( i + int(ci) );
------------------------
In function( int && r )
function( int(i + ci) );
------------------------
In function( int && r )
Results of strings calling functions
Here’s a final bonus scenario! All the results so far have been created on VS2010. If we do ‘function( “Hello” )’ on VS2010, even though an unnamed temporary std::string is created, the const-ness of the initial const char * string causes the const std::string & function to be called:
function( "Hello" );
--------------------
In function( const std::string & r )
This is how an early version of the C++11 spec was documented to work. This has been changed in the published version of C++11 and VS11 acts accordingly:
function( "Hello" );
--------------------
In function( std::string && r )
Conclusions
To conclude, an r-value ref in a function argument is only significant when deciding which of a set of overloaded functions will be called. Once inside a function with an r-value ref the reference is treated as a regular reference.
Put another way, if a variable has a name, even if it is of type r-value ref (e.g. mover &&) it will call the regular ref version of a function, unless you use std::move. This makes sense because even if your function takes an r-value ref parameter you may want to pass the reference to a number of other functions (for example a size() function) before finally moving it on to its destination. Not moving a value on unless you use std::move() achieves this.
Note that you can not do something like:
mover && rref_mover = mover_a;
Instead you would need to do:
mover && rref_mover = std::move( mover_a );
But even then calling a function with rref_mover will not call a function expecting an r-value ref.
r-value ref versions of a function will only be called if a value has no name (for example it is an intermediate result of an expression, a return value of a function, or an implicitly constructed object that is passed as a function argument) or you wrap it with std::move().
This exercise has helped me understand r-value move semantics better and I hope it’s helped you. I hope you’ll find that, while powerful, this new mechanism isn’t difficult to understand after all.
Please share: 
About Codalogic
Codalogic is an ISV specialising in developing products that enhance software developer productivity. Our flagship product is a C++ XML data binding tool designed to make it easier for C++ developers to interact with XML data in their programs. It readily enables storing application configuration data in standard XML format and also reading and writing industry standard XML data.