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?
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.
You should not expect a move c'tor, it's up to compiler.
Since c++17 you cannot expect it, it cannot be called.
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.
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.
Related
In C++, are these two styles of initializing a class-object functionally equivalent, or are there some situations where they might have differing semantics and generate different code?
SomeClass foo(1,2,3);
vs
auto foo = SomeClass(1,2,3);
The 1st one is direct initialization.
The 2nd one is copy initialization, in concept foo is copy-initialized from the direct-initialized temporary SomeClass. (BTW it has nothing to do with operator=; it's initialization but not assignment.)
Because of mandatory copy elision (since C++17) they have the same effect exactly, the object is initialized by the appropriate constructor directly.
In the initialization of an object, 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
Before C++17 copy elision is an optimization; even the copy/move construction might be omitted the appropriate copy/move constructor has to be usable; if not (e.g. the constructor is marked as explicit) the 2nd style won't work while the 1st is fine.
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:
So, code first:
#include <iostream>
#include <utility>
struct X{
int i;
void transform(){}
X() :i(0){std::cout<<"default\n";}
X(const X& src): i(src.i){std::cout<<"copy\n";}
X(X&& msrc) :i(msrc.i){msrc.i=0;std::cout<<"move\n";}
};
X getTransform(const X& src){
X tx(src);
tx.transform();
return tx;
}
int main(){
X x1;// default
X x2(x1); // copy
X x3{std::move(X{})}; // default then move
X x41(getTransform(x2)); // copy in function ,then what?
X x42(std::move(getTransform(x2))); // copy in funciton, then move
X x51( (X()) );//default, then move? or copy?
// extra() for the most vexing problem
X x52(std::move(X())); //default then move
std::cout<<&x41<<"\t"<<&x51<<std::endl;
}
Then ouput from cygwin + gcc 4.8.2 with C++11 features turned on:
default
copy
default
move
copy
copy
move
default
default
move
0x22aa70 0x22aa50
What I don't quite get is the line for x41 and x51. For x41, should the temporary returned from the function call invoke the move constructor or the copy? Same question for x51.
A second question is that, by looking at the output, the constructions of x41 and x51 didn't call any constructors defined, but the objects are clearly created as they reside in memory. How could this be?
An unnamed object matches && better than const&, naturally.
Otherwise move semantics would not work.
Now, there are fewer calls to your default/copy/move-constructors, then one might naively expect, because there's a special rule allowing the ellision of copies, without regard to observable behavior (which must otherwise be preserved by optimizations):
12.8 Copying and moving of objects § 31
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.123 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
Still, if it is returned from a function and directly used to initialize an object of the same type, that move will be omitted.
— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value.
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move.
- [... 2 more for exception handling]
So, going through your list:
X x1;// default
// That's right
X x2(x1); // copy
// Dito
X x3{std::move(X{})}; // default then move
// Yes. Sometimes it does not pay to call `std::move`
X x41(getTransform(x2)); // copy in function ,then what?
// Copy in function, copy to output, move-construction to x41.
// RVO applies => no copy to output, and no dtor call for auto variable in function
// Copy ellision applies => no move-construction nor dtor of temporary in main
// So, only one time copy-ctor left
X x42(std::move(getTransform(x2))); // copy in funciton, then move
// `std::`move` is bad again
X x51( (X()) );//default, then move? or copy? // extra() for the most vexing problem
// Copy-elision applies: default+move+dtor of temporary
// will be optimized to just default
X x52(std::move(X())); //default then move
// And again `std::`move` is a pessimization
I thought using static_cast might avoid binding the temporary, meaning the move can be ellided, but no such luck: 1376. static_cast of temporary to rvalue reference Thanks #dyp for unearthing this issue.
According to the standard § 12.8 [Copying and moving class objects]
31 When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object, even if the constructor selected for the copy/move operation and/or the destructor for the object
have side effects. In such cases, the implementation treats the source and target of the omitted copy/move
operation as simply two different ways of referring to the same object, and the destruction of that object
occurs at the later of the times when the two objects would have been destroyed without the optimization.124
This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which
may be combined to eliminate multiple copies)
in a return statement in a function with a class return type, when the expression is the name of a
non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified
type as the function return type, the copy/move operation can be omitted by constructing
the automatic object directly into the function’s return value.
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the omitted copy/move
Thus in the both cases (i.e., x41, x51 respectively) you are experiencing a copy elision optimization effect.
I think it's simply Return Value Optimization kicking in. You create a copy within the functions on X tx(src);, and then this local variable is just given back to the main. Semantically as a copy, but in fact the copy operation is omitted.
As others have said, moves can also be omitted.
This question already has answers here:
Using std::move() when returning a value from a function to avoid to copy
(3 answers)
Closed 9 years ago.
Let there be a class A with a move constructor. Consider this:
A get()
{
A a;
return std::move( a );
}
// later in the code
A aa = get();
Here the explicit call to std:move forces the move constructor of A to be called thus it might inhibit the return value optimization in while calling get(). Thus it is said the a better implementation of get() would be this:
A get()
{
A a;
return a;
}
But the return value optimization is not a part of C++11 standard, so WHAT IF the compiler, by some reason, decides not to perform return value optimization while calling get(). In this case a copy constructor of A will be called while returning in get() right?
So isn't the first implementation of get() more pereferible??
A compiler should use a move constructor, but I didn't see an obligation in the standard :
It's always said "Copy/move constructor" in the section concerning temporary objects
standard ISO/IEC 14882:2011 C++ :
"
12.1/9
A copy constructor (12.8) is used to copy objects of class type. A move constructor (12.8) is used to move
the contents of objects of class type.
12.8/32
When the criteria for elision of a copy operation are met or would be met save for the fact that the source
object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to
select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload
resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to
the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an
lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will
occur. It determines the constructor to be called if elision is not performed, and the selected constructor
must be accessible even if the call is elided. — end note ]
"
lvalue = T &
rvalue = T &&
So, It says that first, the compiler will look if it find a move constructor, then, it will look for a move constructor.
Thus, if your compiler is conform to the standard, it will call the move constructor.
I append just that which is interesting:
"
12.8/31
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object, even if the copy/move constructor and/or destructor for the object have side effects.
"
...So even if there is side effects in these constructors/destructors, they can be skipped
Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
The function selected is called with the initializer expression as
its argument; if the function is a constructor, the call initializes a
temporary of the cv-unqualified version of the destination type. The
temporary is a prvalue. The result of the call (which is the temporary
for the constructor case) is then used to direct-initialize, according
to the rules above, the object that is the destination of the
copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. In
such cases, the implementation treats the source and target of the
omitted copy/move operation as simply two different ways of referring
to the same object, and the destruction of that object occurs at the
later of the times when the two objects would have been destroyed
without the optimization. This elision of copy/move operations,
called copy elision, is permitted in the following circumstances
(which may be combined to eliminate multiple copies):
[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be
omitted by constructing the temporary object directly into the target of the omitted copy/move
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]
Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
The function selected is called with the initializer expression as
its argument; if the function is a constructor, the call initializes a
temporary of the cv-unqualified version of the destination type. The
temporary is a prvalue. The result of the call (which is the temporary
for the constructor case) is then used to direct-initialize, according
to the rules above, the object that is the destination of the
copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the copy/move
constructor and/or destructor for the object have side effects. In
such cases, the implementation treats the source and target of the
omitted copy/move operation as simply two different ways of referring
to the same object, and the destruction of that object occurs at the
later of the times when the two objects would have been destroyed
without the optimization. This elision of copy/move operations,
called copy elision, is permitted in the following circumstances
(which may be combined to eliminate multiple copies):
[...]
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be
omitted by constructing the temporary object directly into the target of the omitted copy/move
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]