关于结构构造函数和析构函数行为 - C++

Concerning Struct Constructor and Destructor behavior - C++

我不明白为什么这个程序的输出是这样的。为什么没有编译错误?我以为在尝试构造B时,编译器会发现没有调用foo()的函数并报错。

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

输出:

B
A
0
Bye A
Bye B

一般来说,我想知道当存在多重继承时,父结构体的构造和初始化顺序是怎样的?我也可以在 类 中期待类似的行为吗?

非常感谢任何关于构造函数和析构函数调用顺序的解释。

注意:这不是作业。而且,我研究过类似的主题,但没有找到关于这个问题的答案。

未定义的行为

您在对象完全初始化之前通过调用 foo 来调用未定义的行为。引用 C++ 标准中的 12.6.2 :

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 ]

换句话说,按照标准是可以的:

C(int i=0) : B(), A(i) {
    B::b = foo();
}

这将打印 10 而不是你得到的 0 (可能是其他任何东西,因为那是未定义的行为)。

初始化顺序

抛开未定义行为这个问题,为了解决您的问题,初始化发生的顺序是 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 ]

所以,在你的代码中,初始化顺序是:BB::b),AA::a),C()。

不过,正如下面的评论所述,更改此初始化顺序(例如,使用 struct C : A, B 而不是 struct C : B, A)不会消除未定义的行为。在 B 部分初始化之前调用 A::foo 仍然未定义,即使 A 部分已初始化。

这只是未定义行为的另一种情况。例如,我的系统给出了以下结果。

B
A
-858993460
Bye A
Bye B

试试这个 live demo,它产生了另一个不同的结果(C(10).bar() 产生了 32764)。

foo()可以在这个上下文中调用,但是会在A的构造函数之前被调用。这意味着 a 已初始化,这会导致读取未初始化的变量,从而导致未定义的行为。这类似于在初始化之前访问成员。考虑以下示例。 a被初始化为b的值,然后b被初始化。问题很明显,b 在读取初始化 a.

时未初始化
struct foo
{
    foo(int x) : a(b), b(x) {}
    int a;
    int b;
};

int main()
{
    foo bar(10);
}

我想看看到底发生了什么。既然我有结果,就给你。

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

在编译过程中给出以下结果(稍微清理了一下):

>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]

并给出以下输出:

>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