Reputation: 643
Recently I have been studying the move semantics in C++11. I was so impressed that I could not wait to get my hands dirty and try them. The following is my code:
#include <iostream>
using namespace std;
class ArrayWrapper
{
public:
// default constructor produces a moderately sized array
ArrayWrapper ()
: _p_vals( new int[ 64 ] )
, _size( 64 )
{
cout << "Default constructor: " << this << endl;
}
explicit ArrayWrapper (int n)
: _p_vals( new int[ n ] )
, _size( n )
{
cout << "Constructor: " << this << endl;
}
// move constructor
ArrayWrapper (ArrayWrapper&& other)
: _p_vals( other._p_vals )
, _size( other._size )
{
cout << "Move constructor: " << this << endl;
other._p_vals = NULL;
other._size = 0;
}
// copy constructor
ArrayWrapper (const ArrayWrapper& other)
: _p_vals( new int[ other._size ] )
, _size( other._size )
{
cout << "Copy constructor: " << this << endl;
for ( int i = 0; i < _size; ++i )
{
_p_vals[ i ] = other._p_vals[ i ];
}
}
~ArrayWrapper ()
{
cout << "Destructor: " << this << endl;
delete [] _p_vals;
}
void self () {
cout << "This address: " << this << endl;
}
public:
int *_p_vals;
int _size;
};
ArrayWrapper two() {
ArrayWrapper a(7);
cout << "Temp ArrayWrapper created!" << endl;
return a;
}
int main() {
ArrayWrapper b (two());
b.self();
}
(I referenced some code from 1)
The code may look long, but it is actually pretty naive, just a dump array.
On line 67, I deliberately created b with a rvalue and expected to see how the move constructor is called. But disappointingly, the output of this program is:
Constructor: 0x7fff51d60be0
Temp ArrayWrapper created!
This address: 0x7fff51d60be0
Destructor: 0x7fff51d60be0
The three addresses that are printed are the same and the move constructor is not called at all! In fact, I later tried to delete the move constructor, and the program still compiled and gave the same output! And if you look a little carefully, you will find that the constructor is only called once, when constructing a. That is, when b is constructed, no constructor, neither move nor copy, is called at all!
I am really confused. Could anyone please tell me why the move constructor is not triggered, and how on earth b is constructed?
Upvotes: 1
Views: 682
Reputation: 275896
What you are experiencing is known as copy elision -- copy and move constructors can be skipped in certain contexts, even if skipping them would have observable side effects. Common cases include RVO and NRVO (return value optimization and named RVO) or copy-initialization from an anonymous temporary.
To block it, explicitly call std::move
at the point where you return from the function, and where you construct the value in main
, as std::move
's rvalue cast makes elision illegal. std::move
takes a T&
or T&&
and turns it into a T&&
in a way that prevents the "left hand side" from performing elision, as elision is restricted (at this point at least) to certain narrow cases.
It is generally a bad idea to block elision, but std::move
will do it.
Upvotes: 5
Reputation: 7745
It's because of RVO (Return Value Optimization) trick.
Object from ArrayWrapper two();
is created in place of ArrayWrapper b;
.
That is why there is only one construction + destruction.
Try modifying it to something like:
ArrayWrapper two(bool disable_rvo) {
ArrayWrapper a(7);
cout << "Temp ArrayWrapper created!" << endl;
if (disable_rvo)
return a;
else
return ArrayWrapper(8);
}
int main() {
ArrayWrapper b (two(true));
b.self();
}
And turn optimization off.
Upvotes: 5