简体   繁体   中英

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.

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.

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);
}

I wanted to see what was actually happening. Since I have the results, here they are for you.

#include <iostream>
using namespace std;

/*** Structures ***/
struct A{
    int a;
#warning a integer declared in struct A

    A(int i=0) : a(i)
#warning A constuctor declared
#warning A constructor creates variable i, sets it to 0
#warning then assigns i to a
    {
        cout << "inside constructor A" << endl;
        cout << "i = " << i << endl;
        cout << "a = " << a << endl;
        cout << "leaving constructor A" << endl;
    }
#warning A constructor definition provided
    ~A()
#warning A destructor declared
    {
        cout << "inside destructor A" << endl;
        cout << "leaving destructor A" << endl;
    }
#warning A destructor definition provided
    int foo()
#warning foo function declared in struct A
    {
        cout << "inside foo, inside A" << endl;
        cout << "foo will return a = " << a << " then leave foo" << endl;
        return a;
    }
#warning foo function defined in struct A
};

struct B{
    int b;
#warning b integer declared in struct B
    B(int i=0) : b(i)
#warning B constructor declared
#warning B creates int i and initializes it to 0
#warning b is assigned the value of i                 
    {
        cout << "inside constructor B" << endl;
        cout << "i = " << i << endl;
        cout << "b = " << b << endl;
        cout << "leaving constructor B" << endl;
    }
#warning B constructor defined
    ~B()
#warning B destructor declared
    {
        cout << "inside destructor B" << endl;
        cout << "leaving destructor B" << endl;
    }
#warning B destructor defined    
    int bar()
#warning bar function declared in struct B
    {
        cout << "inside bar, inside B" << endl;
        cout << "bar will return b = " << b << " then leave bar" << endl;
        return b;
    }
#warning bar function defined in struct B
};


struct C : B, A 
#warning C structure declared derived from B and A
{
    C(int i=0) : B(foo()), A(i) 
#warning C constructor declared
#warning C constructor creates int i and assigns value 0
#warning C constructor instantiates B and calls foo from A to assign value?
#warning C constructor instantiates A by assigning i to it
    {
        cout << "inside constructor C" << endl;
        cout << "i = " << i << endl;
        cout << "leaving constructor C" << endl;
    }
#warning C constructor defined with no implementation
};

int main() {
    cout << "command is: print the value of C(10).bar()" << endl;
    cout << C(10).bar() << endl;
#warning initialize C with a value of 10
#warning then call the bar function extended from B

#warning declare struct C blah, initialized with C(12)
    cout << endl << "creating struct blah with definition C(12)" << endl;
    struct C blah = C(12);
    cout << "calling blah.foo" << endl;
    cout << blah.foo() << endl;
    cout << "calling blah.bar" << endl;
    cout << blah.bar() << endl;
#warning  printing and then returning 0
    cout << endl << "Some random output before returning 0" << endl;
    return 0;
}

Gives the following during compilation results (cleaned up a little):

>make
test.cpp:7:2:  warning: #warning a integer declared in struct A [-Wcpp]
test.cpp:10:2: warning: #warning A constuctor declared [-Wcpp]
test.cpp:11:2: warning: #warning A constructor creates variable i, sets it to 0 [-Wcpp]
test.cpp:12:2: warning: #warning then assigns i to a [-Wcpp]
test.cpp:19:2: warning: #warning A constructor definition provided [-Wcpp]
test.cpp:21:2: warning: #warning A destructor declared [-Wcpp]
test.cpp:26:2: warning: #warning A destructor definition provided [-Wcpp]
test.cpp:28:2: warning: #warning foo function declared in struct A [-Wcpp]
test.cpp:34:2: warning: #warning foo function defined in struct A [-Wcpp]
test.cpp:39:2: warning: #warning b integer declared in struct B [-Wcpp]
test.cpp:41:2: warning: #warning B constructor declared [-Wcpp]
test.cpp:42:2: warning: #warning B creates int i and initializes it to 0 [-Wcpp]
test.cpp:43:2: warning: #warning b is assigned the value of i [-Wcpp]
test.cpp:50:2: warning: #warning B constructor defined [-Wcpp]
test.cpp:52:2: warning: #warning B destructor declared [-Wcpp]
test.cpp:57:2: warning: #warning B destructor defined [-Wcpp]
test.cpp:59:2: warning: #warning bar function declared in struct B [-Wcpp]
test.cpp:65:2: warning: #warning bar function defined in struct B [-Wcpp]
test.cpp:70:2: warning: #warning C structure declared derived from B and A [-Wcpp]
test.cpp:73:2: warning: #warning C constructor declared [-Wcpp]
test.cpp:74:2: warning: #warning C constructor creates int i and assigns value 0 [-Wcpp]
test.cpp:75:2: warning: #warning C constructor instantiates B and calls foo from A to assign value? [-Wcpp]
test.cpp:76:2: warning: #warning C constructor instantiates A by assigning i to it [-Wcpp]
test.cpp:82:2: warning: #warning C constructor defined with no implementation [-Wcpp]
test.cpp:88:2: warning: #warning initialize C with a value of 10 [-Wcpp]
test.cpp:89:2: warning: #warning then call the bar function extended from B [-Wcpp]
test.cpp:91:2: warning: #warning declare struct C blah, initialized with C(12) [-Wcpp]
test.cpp:98:2: warning: #warning printing and then returning 0 [-Wcpp]

And gives the following output:

>test
command is: print the value of C(10).bar()
inside foo, inside A
foo will return a = 4201198 then leave foo
inside constructor B
i = 4201198
b = 4201198
leaving constructor B
inside constructor A
i = 10
a = 10
leaving constructor A
inside constructor C
i = 10
leaving constructor C
inside bar, inside B
bar will return b = 4201198 then leave bar
4201198
inside destructor A
leaving destructor A
inside destructor B
leaving destructor B

creating struct blah with definition C(12)
inside foo, inside A
foo will return a = 4201104 then leave foo
inside constructor B
i = 4201104
b = 4201104
leaving constructor B
inside constructor A
i = 12
a = 12
leaving constructor A
inside constructor C
i = 12
leaving constructor C
calling blah.foo
inside foo, inside A
foo will return a = 12 then leave foo
12
calling blah.bar
inside bar, inside B
bar will return b = 4201104 then leave bar
4201104

Some random output before returning 0
inside destructor A
leaving destructor A
inside destructor B
leaving destructor B

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM