I have a simple function:
void foo(atomic<int> a) { }
It occurs that I can call foo() in this way:
foo({ });
but I can't do it in a such way:
foo(atomic<int>{ }); due to an error message copy constructor is a deleted function. I have compiled with C++14.
What constructor is called in the first way: create constructor or move constructor?
Why in the second way is not called for example a move constructor?
These two calls of foo() compiles fine with C++17 - why?
In foo({ });, the parameter is copy list-initialized. As the result it's initialized by the default constructor of std::atomic.
In foo(atomic<int>{ });, the parameter is copy-initialized from the temporary std::atomic (i.e. atomic<int>{ }). Before C++17 even the copy/move operation might be elided the copy/move constructor still has to be present and accessible. Since C++17 this is not required again because of mandatory copy elision, it's guaranteed that the parameter is initialized by the default constructor of std::atomic directly.
First, if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision (since C++17)
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: (since C++17)
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:
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.
GCC 7.2 and Clang 5.0 do not agree in this case:
struct A;
A foo();
struct A
{
static void bar()
{
foo();
}
private:
~A()=default;
};
A foo()
{
return {};
//GCC error: A::~A() is private in this context.
};
This behavior is part of the "c++17 guaranteed (copy elision and is not related RVO or NRVO)."
GCC does not compile this code but Clang does. Which one is wrong?
Maybe this paragraph says that the bot Clang and GCC are standard compliant [class.temporary]:
When an object of class type X is passed to or returned from a function, if each copy constructor, move
constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move
constructor, implementations are permitted to create a temporary object to hold the function parameter or
result object. The temporary object is constructed from the function argument or return value, respectively,
and the function’s parameter or return object is initialized as if by using the non-deleted trivial constructor
to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution
to perform a copy or move of the object). [ Note: This latitude is granted to allow objects of class type to be
passed to or returned from functions in registers. — end note ]
I believe this is a clang bug.
From [class.temporary]:
Temporary objects are created [...] when needed by the implementation to pass or return an object of trivially-copyable type (see below), and [...]
Even when the creation of the temporary object is unevaluated ([expr.prop]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed. [ Note: This includes accessibility and whether it is deleted, for the constructor selected and for the destructor. However, in the special case of the operand of a decltype-specifier ([expr.call]), no temporary is introduced, so the foregoing does not apply to such a prvalue. — end note ]
The copy-initialization of the return of foo is a context that creates a temporary object, so the semantic restrictions must still be followed - which include the accessibility of the destructor (as the note helps make clear). ~A() must be accessible in this context, and isn't, so the program should be ill-formed. gcc is correct to reject.
I believe this is a gcc bug in C++17.
According to copy elision:
since C++17
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. They need not be present or accessible,
as the language rules ensure that no copy/move operation takes
place, even conceptually:
In initialization, if the initializer expression is a prvalue and the
cv-unqualified version of the source type is the same class as
the class of the destination, the initializer expression is used to
initialize the destination object.
In a function call, if the operand of a return statement is a
prvalue and the return type of the function is the same as the
type of that prvalue.
Your function foo returns a prvalue object of type A and the return
type of foo is A, no matter whether A has accessible copy/move
constructor and destructor, the copy will be omitted in C++17.
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.
Compare these two initialization methods in C++ for a trivial complex number class.
complx one(1,3);
complx two = complx(3,4);
In the second case, will I get a constructor followed by an assignment, followed by a copy, or just the constructor?
Is it possible to distinguish the two types of initializations ?
complx two = complx(3,4);
This is a copy-initialization. The particular semantics for this initializer are covered by this rule:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as [...] the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). [...]
That is, a copy-initialization where the source type is the same as the destination type behaves like a direct-initialization. So that makes the declaration equivalent to:
complx two(complx(3,4));
This constructs a temporary complx object and then uses the copy/move constructor to construct two.
However, this copy/move may be elided:
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
However, the copy constructor must still be accessible, as though it were being called.
Can the two initializations be distinguished? Assuming the copy/move is valid, not reliably, no. If the compiler does elide the copy in the copy-initialization or if the copy is well behaved and doesn't have any extra side effects, then both will behave exactly the same. If it does not elide the copy and the copy has some extra side effects, then you will notice a difference.
First is known as Direct Initialization.
It simply calls the best match constructor.
Second case is known as Copy initialization.
it creates a object of type Complx and then copies that object to create the two object.
so it involves call to the constructor followed by a copy constructor call.
The compilers are allowed to elide the copy constructor call in general whenever they can(there are special conditions, the code example meet those conditions). But none of the optimizations are guaranteed and they depend on the efficiency of the compiler implementation so the copy constructor needs to be available for second example to compile.
Is it possible to distinguish the two types of initializations ?
Yes, second example will not compile unless a copy constructor is available and accessible.
Good Read:
GotW #36: Initialization
In C++ you can distinguish the two types of initializations by implementing a move constructor or a copy constructor like this
struct complx
{
complx(complx && aOther){} // <- move constructor (C++11)
complx(const complx & aOther){} // <- copy constructor (C++03)
complx(float a, float b) {} // <- normal constructor
};
Your first line of code will call the normal constructor.
Your second line of code is called copy initialization with an rvalue, and will call the move constructor in C++11 and the copy constructor in C++03. Note that in C++03, complx(complx && aOther){} is not recognized (invalid syntax), and should be removed.