Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concerning Struct Constructor and Destructor behavior - C++

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.

like image 681
RoaaGharra Avatar asked Jan 25 '17 13:01

RoaaGharra


People also ask

What is constructor and destructor in C?

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.

Can C structs have constructors?

Constructor creation in structure: Structures in C cannot have a constructor inside a structure but Structures in C++ can have Constructor creation.

Does structure have constructor or destructor?

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.

What is constructor and destructor in data structure?

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.


2 Answers

Undefined behavior

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 a dynamic_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).

Initialization order

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.

like image 147
Sander De Dycker Avatar answered Oct 03 '22 02:10

Sander De Dycker


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);
}
like image 38
François Andrieux Avatar answered Oct 03 '22 00:10

François Andrieux