Order of member constructor and destructor calls
Asked Answered
H

5

162

Oh C++ gurus, I seek thy wisdom. Speak standardese to me and tell my if C++ guarantees that the following program:

#include <iostream>
using namespace std;

struct A
{
    A() { cout << "A::A" << endl; }
    ~A() { cout << "A::~" << endl; }
};

struct B
{
    B() { cout << "B::B" << endl; }
    ~B() { cout << "B::~" << endl; }
};

struct C
{
    C() { cout << "C::C" << endl; }
    ~C() { cout << "C::~" << endl; }
};

struct Aggregate
{
    A a;
    B b;
    C c;
};

int main()
{
    Aggregate a;
    return 0;
}

will always produce

A::A
B::B
C::C
C::~
B::~
A::~

In other words, are members guaranteed to be initialized by order of declaration and destroyed in reverse order?

Head answered 12/2, 2010 at 18:44 Comment(1)
This is a reasonably common cause of subtle bugs when classes have grown large and unweildy. When you have 50 data members, and a lot of them initialized in the constructor intializer list, it can be easy to assume the order of construction is the order in the initializer list. After all, the code writers have ordered the list carefully...haven't they?Ironsmith
H
172

In other words, are members guaranteed to be initialized by order of declaration and destroyed in reverse order?

Yes to both. See 12.6.2

6 Initialization shall proceed in the following order:

  • First, and only for the constructor of the most derived class as described below, virtual base classes shall be 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 class names in the derived class base-specifier-list.

  • Then, direct base classes shall be 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 shall be 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 ]

Hunter answered 12/2, 2010 at 18:50 Comment(2)
If I remember correctly, yes to both... Think of it as a stack. First pushed, last poped. So, when instantiating your first instance, it is pushed into memory in order of a stack. Then, the second is pushed over, the third over the second and so forth. Then, when destroying your instances, the program shall look for the first to destroy, the last that has been pushed. But I might be wrong explaning it this way, but it is the way I learned it when doing C/C++ and ASM.Inverse
isocpp.org/wiki/faq/dtors#order-dtors-for-membersSuspensoid
F
31

Yes, they are (non-static members that is). See 12.6.2/5 for initialization (construction) and 12.4/6 for destruction.

Funicular answered 12/2, 2010 at 18:47 Comment(0)
C
19

Yes, the standard guarantees objects get destructed in the reverse order they were created. The reason is that one object may use another, thus depend on it. Consider:

struct A { };

struct B {
 A &a;
 B(A& a) : a(a) { }
};

int main() {
    A a;
    B b(a);
}

If a were to destruct before b then b would hold an invalid member reference. By destructing the objects in the reverse order in which they were created we guarantee correct destruction.

Callisthenics answered 12/2, 2010 at 19:4 Comment(3)
I never actually considered that this rule applies to the destruction order of scope members as well!Lathy
@Lathy What are the so-called scope members?Vigil
@Vigil in the example above, the two stack variables in main() a and b are destroyed in the reverse order they are declared. Maybe scope members is not a real term but that's how I think about the objects encapsulated in a given scope (in this case, the body of main())Lathy
F
9

Yes and yes. The order of destruction is always opposite to the order of construction, for member variables.

Foreordination answered 12/2, 2010 at 18:47 Comment(0)
E
0

About destructors. Here is the 12.4.8 paragraph of the standard, that proves second "yes":

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).

But please note, that containers usually don't track contents' chronology, so they may behave in non-intuitive way. For example std::vector may destruct its objects from begin to the end, despite the fact, that usually they are filled with push_back() or something like that. Thusly, you cannot implement ctor-dtor stack by containers.

This is my little illustration:

#include <vector>
#include <stdio.h>

struct c
{
        c() : num(++count) { fprintf(stderr, "ctor[%u], ", num);}
        ~c(){ fprintf(stderr, "dtor[%u], ", num);}
private:
        static unsigned count;
        unsigned num;
};
unsigned c::count = 0; 
int main()
{
        std::vector<c> v(5);
}

... and I got: ctor[1], ctor[2], ctor[3], ctor[4], ctor[5], dtor[1], dtor[2], dtor[3], dtor[4], dtor[5],

Extant answered 6/7, 2022 at 11:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.