Reputation: 21965
My custom implemented Integer class is below :
class Integer
{
private:
int * ptr_int_; // Resource
public:
// Other ctors
Integer(Integer &&); // Move constructor
// dtor
}
The move constructor is implemented as below :
Integer::Integer(Integer && arg)
{
std::cout << "Integer(Integer && arg) called\n";
this->ptr_int_ = arg.ptr_int_; // Shallow Copy
arg.ptr_int_ = nullptr;
}
In my driver, for the below call,
Integer obj2{ Integer{5}};
I expected a parameterized constructor(for the temporary object) and then move constructor to be invoked. However the move constructor wasn't invoked.
In disassembly, i got the stuff shown below :
Integer obj2{ Integer{5}};
001E1B04 push 4
001E1B06 lea ecx,[obj2]
001E1B09 call Integer::__autoclassinit2 (01E1320h)
001E1B0E mov dword ptr [ebp-114h],5
001E1B18 lea eax,[ebp-114h]
001E1B1E push eax
001E1B1F lea ecx,[obj2] ;; Is this copy elision(RVO) in action?
001E1B22 call Integer::Integer (01E12FDh)
001E1B27 mov byte ptr [ebp-4],1
I guess, this is Return Value Optimization(RVO) in action. Am I right?
Since most compilers implement RVO, I shouldn't be doing
Integer obj2{ std::move(Integer{5})};
Should I?
Upvotes: 1
Views: 99
Reputation: 16090
This is a tricky one, because it technically changed in c++17. In c++11 it is a NRVO optimization, but in c++17 it's not even an optimization anymore.
Relevant excerpt from cppreference:
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:
[...]
In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
Emphasis is mine on the important part. Above paragraph is in place since c++17 and non existent in c++11.
Now, c++11:
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:
[...]
In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object. When the nameless temporary is the operand of a return statement, this variant of copy elision is known as RVO, "return value optimization". (until c++17)
This is your case. So for c++11 it is RVO for initialization. return
statement RVO is actually covered by another bullet.
Upvotes: 2
Reputation: 941
The move constructor is not invoked due to RVO just as you figured out yourself.
Note that std::move
simply casts its argument into an rvalue reference. In your example Integer{5}
is an unnamed temporary and already an rvalue. The extra call to std::move
is therefore unnecessary. The move constructor will be called anyway, if it is not completely elided as in your case.
Also, note that in your implementation of the move constructor the extra std::move
is unnecessary since ptr_int_
is a raw pointer without any special move semantics.
Upvotes: 2