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.
Related
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)
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:
Consider the following:
struct X {
X() {}
X(X&&) { puts("move"); }
};
X x = X();
In C++14, the move could be elided despite the fact that the move constructor has side effects thanks to [class.copy]/31,
This elision of copy/move operations ... is permitted in the following circumstances ... 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
In C++17 this bullet was removed. Instead the move is guaranteed to be elided thanks to [dcl.init]/17.6.1:
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. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. — end
example ]
Thus far the facts I've stated are well-known. But now let's change the code so that it reads:
X x({});
In C++14, overload resolution is performed and {} is converted to a temporary of type X using the default constructor, then moved into x. The copy elision rules allow this move to be elided.
In C++17, the overload resolution is the same, but now [dcl.init]/17.6.1 doesn't apply and the bullet from C++14 isn't there anymore. There is no initializer expression, since the initializer is a braced-init-list. Instead it appears that [dcl.init]/(17.6.2) applies:
Otherwise, 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, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors are enumerated (16.3.1.3),
and the best one is chosen through overload resolution (16.3). The constructor so selected is called
to initialize the object, with the initializer expression or expression-list as its argument(s). If no
constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
This appears to require the move constructor to be called, and if there's a rule elsewhere in the standard that says it's ok to elide it, I don't know where it is.
As T.C. points out, this is in a similar vein to CWG 2327:
Consider an example like:
struct Cat {};
struct Dog { operator Cat(); };
Dog d;
Cat c(d);
This goes to 11.6 [dcl.init] bullet 17.6.2:
Otherwise, 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, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (16.3.1.3 [over.match.ctor]), and the best one is chosen through overload resolution (16.3 [over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Overload resolution selects the move constructor of Cat. Initializing the Cat&& parameter of the constructor results in a temporary, per 11.6.3 [dcl.init.ref] bullet 5.2.1.2. This precludes the possitiblity of copy elision for this case.
This seems to be an oversight in the wording change for guaranteed copy elision. We should presumably be simultaneously considering both constructors and conversion functions in this case, as we would for copy-initialization, but we'll need to make sure that doesn't introduce any novel problems or ambiguities.
What makes this the same underlying problem is that we have an initializer (in OP, {}, in this example, d) that's the wrong type - we need to convert it to the right type (X or Cat), but to figure out how to do that, we need to perform overload resolution. This already gets us to the move constructor - where we're binding that rvalue reference parameter to a new object that we just created to make this happen. At this point, it's too late to elide the move. We're already there. We can't... back up, ctrl-z, abort abort, okay start over.
As I mentioned in the comments, I'm not sure this was different in C++14 either. In order to evaluate X x({}), we have to construct an X that we're binding to the rvalue reference parameter of the move constructor - we can't elide the move at that point, the reference binding happens before we even know we're doing a move.
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