Copy elision when initializing a base-class subobject with aggregate initialization - c++

In the following code, struct B is an aggregate with base struct A, and B-object is aggregate initialized B b{ A{} }:
#include <iostream>
struct A {
A() { std::cout << "A "; }
A(const A&) { std::cout << "Acopy "; }
A(A&&) { std::cout << "Amove "; }
~A() { std::cout << "~A "; }
};
struct B : A { };
int main() {
B b{ A{} };
}
GCC and MSVC perform A copy elision, printing:
A ~A
while Clang creates a temporary and moves it, printing:
A Amove ~A ~A
Demo: https://gcc.godbolt.org/z/nTK76c69v
At the same time, if one defines struct A with deleted move/copy constructor:
struct A {
A() {}
A(const A&) = delete;
A(A&&) = delete;
~A() {}
};
then both Clang (expected) and GCC (not so expected in case of copy elision) refuse to accept it, but MSVC is still fine with it. Demo: https://gcc.godbolt.org/z/GMT6Es1fj
Is copy elision allowed (or even mandatory) here? Which compiler is right?
There is a related question Why isn't RVO applied to base class subobject initialization? posted 4 years ago, but
this question askes about peculiarities of aggregate initialization, not covered there;
as one can see copy elision is applied in the above example by 2 out of 3 tested modern compilers. Is it due to bugs in these compilers (still present after 4 years) or they are allowed doing so?

Related

How do destructors work in a simple program?

I don't understand the output of this program :
class A {
public :
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
A f (A & a) {
return a;
}
int main() {
A a ;
a = f(a);
return 0;
}
I expected
A()
~A()
because I only created one A object : a. However, the output is
A()
~A()
~A()
Do you know why that is ?
FOLLOWUP QUESTION
Okay, so whenever calling f, I construct a copy of an A, so I have 1 call to the copy constructor and one call to the destructor...
Say now my main function is :
int main() {
A a ;
A b = f(a);
cout << "returning 0" << endl;
return 0;
}
I would expect the output to be
A(),
A(const A &) (for using f(a))
~A() (for deleting temporary f(a))
returning 0
~A() (destroying B)
~A() (destroying A)
But the output is
A()
A(const& A)
returning 0
~A()
~A()
Why is that ?
You only created one object explicitly, but you are creating one object here:
A f (A & a) { return a ;} //when returning A
as you are copying the object to pass it back from f, that copy is being constructed by the default copy constructor as you didn't provide one.
If you change your class to this:
class A {
public :
A () { cout << "A() " << endl;}
A (const A &) { cout << "A(const &) " << endl;}
~A () { cout << "~A ()" << endl; }
};
you will see the copy constructor being called (as you are providing one).
An answer to the follow-up question.
Destructors pair with constructors. Why do you expect two constructions and three destructions? That's not possible in a correct program.
A b = f(a);
is not an assignment (in contrast to a = f(a)), but a construction (copy initialization). You don't see a construction and destruction of a temporary object here thanks to the return value optimization (RVO): a compiler is allowed to elide an unnecessary copy and construct b as if by A b(a);.
Before C++17 this mechanism is optional. You can compile this code with GCC with -std=c++11 -fno-elide-constructors options to spot a temporary:
A() construct (a)
A(const A&) construct a temporary copy of (a)
A(const A&) construct (b) from that temporary
~A() destruct that temporary
returning 0
~A() destruct (b)
~A() destruct (a)
Since C++17 this type of copy elision is mandatory, so you'll always see only two constructions and destructions:
A() construct (a)
A(const A&) construct (b) from (a)
returning 0
~A() destruct (b)
~A() destruct (a)

Explicitly defaulted destructor disables default move constructor in a class

I have run into a problem that a move constructor of a superclass did not get invoked properly when its subclass has an explicitly defaulted destructor. The move constructor does get invoked when the destructor is implicitly defaulted (not provided at all in the supclass definition).
I am aware of the constraints that the compilers should apply to default move constructors. Yet, I have been by all means sure that the compiler should not discriminate between explicitly/implicitly defaulted destructors (or constructors as well) when applying these rules. In other words, explicitly defaulted destructor should not be treated as user-defined one (in contrast to an empty user-defined destructor ).
Tested with MSVC 2019 only.
Am I or MSVC right here?
#include <iostream>
class A {
public:
A() = default;
A(const A&) { std::cout << "Auch, they'r making copy of me(?!)" << std::endl; }
A(A&&) { std::cout << "I am moving :)" << std::endl; }
~A() = default;
};
class B : public A {
public:
};
class C : public A {
public:
C() = default;
};
class D : public A {
public:
~D() = default;
};
class E : public A {
public:
E() = default;
E(const E&) = default;
E(E&&) = default;
~E() = default;
};
int main()
{
std::cout << "\n---- A ----\n" << std::endl;
A a;
A a2(std::move(a));
std::cout << "\n---- B ----\n" << std::endl;
B b;
B b2(std::move(b));
std::cout << "\n---- C ----\n" << std::endl;
C c;
C c2(std::move(c));
std::cout << "\n---- D ----\n" << std::endl;
D d;
D d2(std::move(d));
std::cout << "\n---- E ----\n" << std::endl;
E e;
E e2(std::move(e));
}
EXPECTED: Display "I am moving :)" in all cases
ACTUAL : Displays "Auch, they'r making copy of me(?!)" in case D
When you declare a defaulted destructor in D, you disable the compiler-generated move constructor and move assignment operator in D (!), not the base class version. This is why you get the expected output with E, where you override the defaulted operation by the compiler by explicitly = defaulting the special member functions. The compiler-generated move constructor does the right thing for movable base class types, so follow the rule of five and = default the special member functions for D.
Have a look at the table in this answer. It's a very useful reference to be kept under the pillow.

How does the compile choose which constructor to call?

This is my code.
When I delete line 11, the output is
A(0)
B(0)
A(1)
about the last line, "A(1) ", why the second constructor of class A is called?
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A(0)" << endl; }
A(const A& a) { cout << "A(1)" << endl; }
};
class B {
public:
B() : a() { cout << "B(0)" << endl; }
// B(const B& b) { cout << "B(1)" << endl; }
private:
A a;
};
int main() {
B object1;
B object2 = object1;
return 0;
}
A(0)
B(0)
A(1)
When
B(const B& b) { cout << "B(1)" << endl; }
is commented out/deleted the compiler generates a copy constructor for you. This provided copy constructor will copy all of the members of the class so in this case it will stamp out a copy constructor that looks like
B(const B& copy) : a(copy.a) {}
This is why you see a's copy constructor called.
When you do not comment out/delete
B(const B& b) { cout << "B(1)" << endl; }
You do not copy a because you do not tell it to do so. What the compiler does instead is creates a default initialization for it by transforming the constructor to
B(const B& b) : a() { cout << "B(1)" << endl; }
so the default constructor is called instead of the copy constructor.
The compiler is generating a copy constructor for you, which copies the member a. In order to copy member a, it calls its copy constructor in turn, which prints A(1).
Because object2 is initialized with the implicit copy constructor of B. An implicit copy constructor implicitly copy all the data members of the class, hence the call of the copy constructor of A, which prints "A(1)".
The issue you've run into has to do with thinking commenting out line 11 means you've deleted that constructor.
In C++, there are a couple of constructors that are automatically generated if you ended up using them, even if you didn't declare them yourself. The copy-constuctor, which has the same signature as the commented-out constructor in B, is one of them.
In your case, you end up first calling the default constructor for B, which first constructs it's member A using the default constructor as well. This should give the output you see, where the body of A's copy-constructor is reached before the body of B's because of member initialization ordering.
Then, you make a new object of type B using the assignment operator which implicitly calls the now-generated copy constructor of B. That means A's copy constructor gets called as well, which is a rule in how B's copy-constructor is auto generated. With A's copy-constuctor un-commented, it gets called with the printout.
The compiler for the class B (with the commented copy constructor) defines implicitly the default copy constructor that calls copy constructors for class members.
From the C++ 20 Standard (11.3.4.2 Copy/move constructors)
14 The implicitly-defined copy/move constructor for a non-union class
X performs a memberwise copy/move of its bases and members...
The implicitly defined default copy constructor of the class B looks like
B( const B &b ) : a( b.a )
{
}
Here is a demonstrative program
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A(0)" << endl; }
A(const A& a) { cout << "A(1)" << endl; }
};
class B {
public:
B() : a() { cout << "B(0)" << endl; }
// An analogy of the implicitly declared copy constructor
B(const B& b) : a( b.a ){}
private:
A a;
};
int main() {
B object1;
B object2 = object1;
return 0;
}
The program output will be the same as if to remove the copy constructor of class B that corresponds to the implicitly generated copy constructor by the compiler.
A(0)
B(0)
A(1)

why does adding a move constructor disable initializier list?

With a simple struct such as
struct Foo { int i; };
I can create a new instance using an initializer list; no need to write a constructor:
Foo foo { 314 };
If I now add a move constructor
struct Bar
{
int i;
Bar(Bar&& other) { i = other.i; }
};
The initializer no longer works and I have to add a constructor too:
Bar(int i) : i(i) {}
I'm guessing this behavior is somewhat related to this answer (for user-defined move-constructor disables the implicit copy-constructor?), but more details would be nice.
Edit: as indicated by the answers, this has to do with adding a constructor. Which in turn would seem to create an inconsistency of sorts, if I add just a move operator:
struct Baz
{
int i;
Baz& operator=(Baz&& other)
{
this->i = other.i;
return *this;
}
};
The initializer works again, although with a slightly different syntax for "move" (yes, this is actually default construct and move assignment; but the end result seems about the same):
Baz baz{ 3141 };
Baz b;
b = std::move(baz);
When there are no constructors this syntax is aggregate initialization because this structure is an aggregate.
When a constructor is added this structure is no longer an aggregate, aggregate initialization cannot be used. The exact rules are listed in list initialization, the relevant ones are:
The effects of list initialization of an object of type T are:
Otherwise, if T is an aggregate type, aggregate initialization is performed.
Otherwise, the constructors of T are considered, in two phases:...
It is not initializer list construction that is disabled by the move constructor (that was not present there to start with), but aggregate construction. And for a good reason: by adding a custom constructor, we indicate to the compiler exactly that the class in not an aggregate, that something different is necessary than just operate on each of its members one by one.
For an aggregate, the default default, copy, and move constructor will work well even if the member variables have nontrivial types. A copy construction will automatically be deleted if it can't be delegated to them, leaving move construction usable:
struct A { // non-copyable
int a;
int b;
A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
A() { std::cout << "A()\n"; }
A(const A&) = delete;
A(A&&) { std::cout << "A(A&&)\n"; }
};
struct B {
A a;
};
int main() {
B b1{{1,2}}; // OK: aggregate
B b2{std::move(b1)}; // OK: calls A::A(A&&)
//B b3{b1}; // error: B::B(const B&) auto-deleted
}
However, if you want to delete copy construction for some other reason and keep the others at default, just be explicit about it:
struct A { // copyable
int a;
int b;
A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
A() { std::cout << "A()\n"; }
A(const A&) { std::cout << "A(const A&)\n"; }
A(A&&) { std::cout << "A(A&&)\n"; }
};
struct B { // non-copyable
A a;
B() = default;
B(const B&) = delete;
B(B&&) = default;
};
int main() {
B b1{{1,2}}; // OK: still an aggregate
B b2{std::move(b1)}; // delegates to A::A(A&&)
//B b3{b1}; // error
}
Because you're using aggregate initialization, which says:
Aggregate initialization is a form of list-initialization, which
initializes aggregates An aggregate is one of the following types:
array type class type (typically, struct or union), that has
no private or protected non-static data members
no user-provided,
inherited, or explicit (since C++17) constructors (explicitly
defaulted or deleted constructors are allowed) (since C++11)
no
virtual, private, or protected (since C++17) base classes
no virtual
member functions
Point 2 makes your case fail.

Does passing reference to itself to constructor of base class causes undefined behavior?

while implementing some design ideas I stumbled upon some strange behavior, which I cannot explain myself.
Take a look at the following example, which has been compiled with MSVC2010.
struct A
{
void helloMe()
{
std::cout << "hej da!\n";
}
};
struct B
{
B(A& a):
m_a(a)
{
std::cout << "B got constructed!\n";
}
A& m_a;
};
struct C : A, B
{
C() :
A(),
B(*this)
{ std::cout << "C got constructed!\n";
}
};
int _tmain()
{
C c;
c.m_a.helloMe();
std::cin.get();
return 0;
}
This compiles error free, but when executing, the constructor of B is never called. That is, when trying to access m_a, I got an segfault. But when explicitly casting to have *reinterpret_cast<A*>(this), the constructors are called as intended.
So why does the above example not work, but I need to explicitly cast? Is this a compiler dependent issue?
EDIT:
So with cast it would look like:
struct A
{
void helloMe()
{
std::cout << "hej da!\n";
}
};
struct B
{
B(A& a):
m_a(a)
{
std::cout << "B got constructed!\n";
}
A& m_a;
};
struct C : A, B
{
C() :
A(),
B(*reinterpret_cast<A*>(this))
{ std::cout << "C got constructed!\n";
}
};
int _tmain()
{
C c;
c.m_a.helloMe();
std::cin.get();
return 0;
}
As jrok pointed out, this is a compiler error. The compiler does not recognize the ambiguous call of the copy constructor and the explicit one.
Further, a static_cast<A*> would have been the right choice there.
So indeed, the cast is necessary according to the standard and MSVC2010 lacks in standard conform behavior. It just calls the copy constructor without even considering the actually declared one. Thus segfaults may occur. You will notice the copy constructor call, when explicitly declaring the copy constructor private. Then MSVC2010 complains.
Thanks for your comments.