I don't understand why the output of this program is as follows. Why isn't there a compilation error? I thought when trying to construct B, the compiler would find no function called foo() and report an error.
#include <iostream>
using namespace std;
struct A{
int a;
A(int i=0) : a(i) { cout << "A" << endl; }
~A() { cout << "Bye A" << endl; }
int foo() { return a; }
};
struct B{
int b;
B(int i=0) : b(i) { cout << "B" << endl; }
~B() { cout << "Bye B" << endl; }
int bar() { return b; }
};
struct C : B, A {
C(int i=0) : B(foo()), A(i) {}
};
int main() {
cout << C(10).bar() << endl;
return 0;
}
The output:
B
A
0
Bye A
Bye B
In general, I would like to know when there is multiple inheritance, what is the order in which the parent structs are constructed and initialized? Can I expect a similar behavior in classes too?
Any explanation regarding the order of constructor and destructor calls is much appreciated.
Note: This is not homework. And, I have researched similar topics but nothing came up regarding this issue.
Constructors are special class functions which performs initialization of every object. The Compiler calls the Constructor whenever an object is created. Constructors initialize values to object members after storage is allocated to the object. Whereas, Destructor on the other hand is used to destroy the class object.
Constructor creation in structure: Structures in C cannot have a constructor inside a structure but Structures in C++ can have Constructor creation.
A structure called Struct allows us to create a group of variables consisting of mixed data types into a single unit. In the same way, a constructor is a special method, which is automatically called when an object is declared for the class, in an object-oriented programming language.
Constructor is used to initialize an object of the class and assign values to data members corresponding to the class. While destructor is used to deallocate the memory of an object of a class.
You're invoking undefined behavior by calling foo
before the object is fully initialized. Quote from 12.6.2 in the C++ standard :
Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the
typeid
operator (5.2.8) or of adynamic_cast
(5.2.7). However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined. [ Example:
class A {
public:
A(int);
};
class B : public A {
int j;
public:
int f();
B() : A(f()), // undefined: calls member function
// but base A not yet initialized
j(f()) { } // well-defined: bases are all initialized
};
class C {
public:
C(int);
};
class D : public B, C {
int i;
public:
D() : C(f()), // undefined: calls member function
// but base C not yet initialized
i(f()) { } // well-defined: bases are all initialized
};
— end example ]
In other words, this would be ok according to the standard :
C(int i=0) : B(), A(i) {
B::b = foo();
}
And this will print 10
instead of the 0
that you got (which could have been anything else, since that was undefined behavior).
Setting aside this matter of undefined behavior, and to address your question, the order in which initialization happens is well-defined :
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
— Finally, the compound-statement of the constructor body is executed.
[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. — end note ]
So, in your code, the initialization order is : B
(B::b
), A
(A::a
), C
().
As noted in the comments below though, changing this initialization order (by eg. using struct C : A, B
instead of struct C : B, A
) would not however get rid of the undefined behavior. Calling A::foo
before the B
part is initialized remains undefined, even if the A
part is initialized.
This is just another case of undefined behavior. For example, my system gives the following results.
B
A
-858993460
Bye A
Bye B
Try this live demo which produces yet another distinct result (C(10).bar()
produced 32764).
foo()
can be called in this context, but it will be called before A
's constructor. This means a
is initialized, which leads to reading an uninitialized variable, which leads to undefined behavior. This is similar to accessing a member before it's initialized. Consider the following example. a
is initialized to b
's value, then b
is initialized. The problem is obvious, b
is uninitialized at the point where it's read to initialize a
.
struct foo
{
foo(int x) : a(b), b(x) {}
int a;
int b;
};
int main()
{
foo bar(10);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With