Unusual behavior of copy ctor - c++

#include<iostream>
#include<string>
using namespace std;
class B;
class A {
public:
A(const A&) { //copy ctor
cout << "Copy ctor A\n";
}
A() {} //default ctor
A(const B&) { //lets call this conversion ctor
cout << "B to A conversion ctor\n";
}
};
class B {
public:
};
int main()
{
B b;
A a = b;
}
The above code prints
B to A conversion ctor.
But as per what I have discovered after looking around for a while, it should print
B to A conversion ctor
Copy ctor A
As first a temporary object of type A is created by conversion ctor and then that object is copied into a wherein copy ctor gets called. Also, when copy ctor is made private, statement A a = b; generates this error:
‘A::A(const A&)’ is private
which is obvious as to copy the temporary object into a copy ctor must be visible.
So my question is why copy ctor is not being eventually called as evident from the output(please correct if am leading wrong somewhere here) even though it is required to be accessible?

A a = b;
This form is called copy-initialization. The applicable rule states that in this case a temporary A object will be constructed from the B instance and that temporary will then be used to direct-initialize a.
However, the compiler is allowed to elide the copy as it is not necessary. Even though the elision is allowed, the class still needs to be copyable for the form to be valid.
You can see the result without elision by passing -fno-elide-constructors to GCC or Clang.

why copy ctor is not being eventually called as evident from the output
According to the standard, an implementation is allowed to omit the copy/move construction in certain criteria. In this case, a temporary A(constructed from b, and will be copied to a) 's construction is omitted.
$12.8/31 Copying and moving class objects [class.copy]
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.
And
(31.3) — 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
type (ignoring cv-qualification), the copy/move operation can be
omitted by constructing the temporary object directly into the target
of the omitted copy/move

Related

Copy constructor implicitly called?

I have the following class with both a normal constructor and copy constructor defined.
#include <iostream>
class Bla
{
public:
Bla()
{
std::cout << "Normal Constructor Called\n";
}
Bla(const Bla& other)
{
std::cout << "Copy Constructor Called\n";
}
};
int main()
{
Bla a = Bla(); // prints Normal Constructor
}
In the main function, it prints the normal constructor as I expected and only the normal constructor. However, if I make the copy constructor a private member of the class, the compiler gives me the error
error: ‘Bla::Bla(const Bla&)’ is private within this context
From the looks of it, it looks like the copy constructor was called, but I do not see anything being printed from it. Is the copy constructor being implicitly called? What's going on here?
Before C++17, the copy operation might be elided but the copy constructor still needs to be present and accessible.
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:
Since C++17 there's no such issue because of mandatory copy elision.
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:

When is the compiler allowed to optimize out the copy-constructor [duplicate]

This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 4 years ago.
Today I encountered something I don't really understand about copy constructor.
Consider the next code:
#include <iostream>
using namespace std;
class some_class {
public:
some_class() {
}
some_class(const some_class&) {
cout << "copy!" << endl;
}
some_class call() {
cout << "is called" << endl;
return *this; // <-- should call the copy constructor
}
};
some_class create() {
return some_class();
}
static some_class origin;
static some_class copy = origin; // <-- should call the copy constructor
int main(void)
{
return 0;
}
then the copy constructor is called when assigning origin to copy, which makes sense. However, if I change the declaration of copy into
static some_class copy = some_class();
it isn't called. Even when I am using the create() function, it does not call the copy constructor.
However, when changing it to
static some_class copy = some_class().call();
it does call the copy constructor.
Some research explained that the compiler is allowed to optimize the copy-constructor out, which sound like a good thing. Until the copy-constructor is non-default, as then it might, or might not do something obvious, right?
So when is it allowed for the compiler to optimize out the copy-constructor?
Since C++17, this kind of copy elision is guaranteed, the compiler is not just allowed, but required to omit the copy (or move) construction, even if the copy (or move) constructor has the observable side-effects.
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(T())); // only one call to default constructor of T, to initialize x
In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() {
return T();
}
f(); // only one call to default constructor of T
Your code snippet matches these two cases exactly.
Before C++17, the compiler is not required, but allowed to omit the copy (or move) construction, even if the copy/move constructor has observable side-effects. Note that it's an optimization, even copy elision takes place the copy (or move) constructor still must be present and accessible.
On the other hand, call() doesn't match any conditions of copy elision; it's returning *this, which is neither a prvalue nor a object with automatic storage duration, copy construction is required and can't be omitted.

Why is the move constructor involved here

I have this piece of C++ code:
class Args {};
class MyClass {
public:
MyClass(Args& a) {}
MyClass(MyClass &&) = delete;
};
int main() {
Args a;
MyClass c1 = MyClass(a);
MyClass c2 = a;
MyClass c3(a);
return 0;
}
This does not compile because the construction of objects c1 and c2 seem to involve the class's move constructor:
error: use of deleted function ‘MyClass::MyClass(MyClass&&)’
It seems as if the compiler wants to create temporary object and then move them to c1 and c2. Why is this happening? Shouldn't all three statements just call the MyClass(Args& a) constructor?
On the other hand, if I do create the move constructor the program compiles fine and the move constructor is never called!!!
See copy elision:
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. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
Since C++17:
They need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually.
One major problem is this:
MyClass c1 = MyClass(a);
This creates a temporary object of type MyClass, and temporary types are rvalues. It then tries to either copy-construct c1 using the temporary object, or if you have a move-constructor then move-construct c1.
In C++11 and C++14, there will be no auto-generated copy-constructor for your class (because you have a user-defined (even if deleted) move-constructor), so only the deleted move-constructor is available. Well it would be available if it wasn't deleted, leading to your error.
Why is this happening? Shouldn't all three statements just call the MyClass(Args& a) constructor?
For both MyClass c1 = MyClass(a); and MyClass c2 = a;, temporary MyClass will be constructed at first by the constructor MyClass::MyClass(Args& a), then used to copy-initialize the object c1 and c2. The constructed temporaries are rvalues, that means the move-constructor will be selected for the copy initialization.
On the other hand, if I do create the move constructor the program compiles fine and the move constructor is never called!!!
The reason is copy elision; the copy/move operation is omitted here, results in the fact that MyClass::MyClass(Args& a) is used to initialize the object c1 and c2 directly.
The rule about copy elision changed since C++17. Note that pre-C++17, copy elision is not guaranteed. And for non-guaranteed copy elision, even when the copy/move operation is omitted the copy/move constructor still needs to be present and accessible.
This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
After C++17 your code would work fine. For guaranteed copy elision, copy/move constructor is not required to be present or accessible.
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:
T x = T(T(T())); // only one call to default constructor of T, to initialize x

Explicitly deleted move constructor

Why this:
struct A
{
A(int) {
cout << "construct from int" << endl;
}
A(A&&) = delete;
A(const A &) {
cout << "copy constructor" << endl;
}
};
int main(){
A a = 0;
}
gives me an error:
error: use of deleted function ‘A::A(A&&)’
And why when I add such move constructor
A(A&&) {
cout << "move constructor" << endl;
}
it compiles fine, but programm's output is just
construct from int
So as far as I understand, compiler asks for constructor but doesn't use it. Why? This makes no sense to me.
P.S. I assume that
A a = 0;
is equvalent of
A a = A(0);
but why neither move constructor nor move assignment operator is called?
According to the C++ 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.122 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
and
30 A program is ill-formed if the copy/move constructor or the
copy/move assignment operator for an object is implicitly odr-used and
the special member function is not accessible (Clause 11). [ Note:
Copying/moving one object into another using the copy/move constructor
or the copy/move assignment operator does not change the layout or
size of either object. —end note ]

Why copy constructor is not called when pass temporary object

class MyClass
{
public:
int a;
MyClass(int r): a(r) {}
MyClass(const MyClass& ref)
{
cout << "Copy Constructor\n";
a= ref.a;
}
};
int main()
{
MyClass obj(5);
MyClass obj1(MyClass(5)); //Case 1
MyClass obj2(obj); //Case 2
return 0;
}
Why the copy constructor is invoked in Case 2, not in Case 1.
In case 1 a temporary object is passed as argument.
In MyClass obj1(MyClass(5)); the compiler elides the temporary object MyClass(5) because it is allowed to.
In particular, C++ standard 2014 §12.8 para 31, defines cases when copy elision can be performed:
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... 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.