I have been trying to write a class that cannot be copied but can be moved, and that cannot be created except with named constructors. I achieved my goal with namedConstructor3
below. However, I do not understand why namedConstructor2
failed.
struct A
{
int a;
//static A && namedConstructor1( int a_A )
//{
// A d_A( a_A );
// return d_A; // cannot convert from A to A&&
//}
static A && namedConstructor2( int a_A )
{
wcout << L"Named constructor 2\n";
A d_A( a_A );
return move( d_A );
}
static A namedConstructor3( int a_A )
{
wcout << L"Named constructor 3\n";
A d_A( a_A );
return move( d_A );
}
A( A && a_RHS ) : a( a_RHS.a )
{
a_RHS.a = 0;
wcout << L"\tMoved: a = " << a << endl;
}
~A()
{
wcout << L"\tObliterated: a = " << a << endl;
a = -a;
}
A( const A & ) = delete;
A & operator =( const A & ) = delete;
protected:
A( int a_A = 0 ) : a( a_A )
{
wcout << L"\tCreated: a = " << a << endl;
}
};
int main()
{
A d_A2 = A::namedConstructor2( 2 );
A d_A3 = A::namedConstructor3( 3 );
wcout << "Going out of scope\n";
return 0;
}
The output is
Named constructor 2
Created: a = 2
Obliterated: a = 2
Moved: a = -2
Named constructor 3
Created: a = 3
Moved: a = 3
Obliterated: a = 0
Going out of scope
Obliterated: a = 3
Obliterated: a = -2
Questions:
Why is the destructor called before the move constructor in namedConstructor2
as evidenced by the 3rd and 4th lines of the output?
Why is the destroyed data still available to the moved constructor (as evidenced by the 4th line of the output)?
Isn't namedConstructor2
"more natural" than namedConstructor3
in the sense that the signature of std::move
leads me to think that the return value of std::move
has "two &&"?
template< class T >
typename std::remove_reference<T>::type&& move( T&& t )
Compiled with VS2013u4.
Edit
Deduplicator's answer satisfies me. This edit is for completeness sake. The answer and the comments suggested that namedConstructor3
was "sub-optimal". I added
static A namedConstructor4( int a_A )
{
wcout << L"Named constructor 4\n";
A d_A( a_A );
return d_A;
}
to the class and A d_A4 = A::namedConstructor4( 4 );
to the main
function. The new output (when compiled in release mode, not in debug mode) shows that the optimal case does not even move the object:
Named constructor 2
Created: a = 2
Obliterated: a = 2
Moved: a = -2
Named constructor 3
Created: a = 3
Moved: a = 3
Obliterated: a = 0
Named constructor 4
Created: a = 4
Going out of scope
Obliterated: a = 4
Obliterated: a = 3
Obliterated: a = -2
std::move(lvalue or xvalue)
does not call a destructor.
It just changes the passed reference to an rvalue-reference, so move-semantics apply.
So, why is your local destroyed too early?
Simple, returning a reference to a local variable is UB:
Can a local variable's memory be accessed outside its scope?
Going over your named constructors one-by-one:
namedConstructor1
returns an rvalue-reference.namedConstructor2
is in principle the same as namedConstructor1
, but you added an explicit cast (in the form of std::move
) from lvalue to rvalue-reference, which shuts the compiler up.namedConstructor3
is ok, though sub-optimal.std::move
disabling return-value-optimization, which would obviate the need for the implementation to actually move (or copy) in this case.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With