Kolejność wywołań konstruktora składowego i destruktora

121

Och, guru C ++, szukam twojej mądrości. Powiedz mi standardowym i powiedz mi, czy C ++ gwarantuje, że następujący 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;
}

zawsze będzie produkować

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

Innymi słowy, czy gwarantuje się, że członkowie zostaną zainicjowani zgodnie z kolejnością deklaracji i zniszczeni w odwrotnej kolejności?

sbk
źródło
8
Jest to dość częsta przyczyna subtelnych błędów, gdy klasy stały się duże i niesprawne. Gdy masz 50 członków danych, a wiele z nich jest zainicjowanych na liście inicjalizatora konstruktora, można łatwo założyć, że kolejność konstrukcji jest zgodna z kolejnością na liście inicjatorów. W końcu piszący kod starannie uporządkowali listę ... prawda?
Permaquid

Odpowiedzi:

140

Innymi słowy, czy gwarantuje się, że członkowie zostaną zainicjowani zgodnie z kolejnością deklaracji i zniszczeni w odwrotnej kolejności?

Tak dla obu. Patrz 12.6.2

6 Inicjalizacja powinna przebiegać w następującej kolejności:

  • Po pierwsze i tylko dla konstruktora klasy najbardziej pochodnej, jak opisano poniżej, wirtualne klasy bazowe powinny być inicjowane w kolejności, w jakiej pojawiają się na przechodzeniu w głąb od lewej do prawej skierowanego acyklicznego grafu klas bazowych, gdzie „lewa -to-right ”to kolejność pojawiania się nazw klas bazowych na liście specyfikatorów-bazowych klas pochodnych.

  • Następnie bezpośrednie klasy bazowe zostaną zainicjowane w kolejności deklaracji, w jakiej pojawiają się na liście specyfikatorów bazowych (niezależnie od kolejności inicjalizatorów pamięci).

  • Następnie niestatyczne składowe danych zostaną zainicjowane w kolejności, w jakiej zostały zadeklarowane w definicji klasy (ponownie niezależnie od kolejności inicjalizatorów pamięci).

  • Na koniec wykonywana jest instrukcja złożona treści konstruktora. [Uwaga: kolejność deklaracji ma na celu zapewnienie, że podobiekty bazowe i składowe zostaną zniszczone w odwrotnej kolejności inicjalizacji. —End note]

dirkgently
źródło
2
Jeśli dobrze pamiętam, tak dla obu ... Potraktuj to jako stos. Pierwszy pchnięty, ostatni popchnięty. Tak więc podczas tworzenia pierwszej instancji jest ona umieszczana w pamięci w kolejności stosu. Następnie druga jest przepychana, trzecia nad drugą i tak dalej. Następnie, niszcząc twoje instancje, program będzie szukał pierwszej, która zniszczy, ostatniej, która została wypchnięta. Ale mogę się mylić, wyjaśniając to w ten sposób, ale jest to sposób, w jaki nauczyłem się tego, robiąc C / C ++ i ASM.
Will Marcouiller,
29

Tak, są (to znaczy niestatyczni członkowie). Patrz 12.6.2 / 5 dla inicjalizacji (konstrukcja) i 12.4 / 6 dla zniszczenia.

Mrówka
źródło
10

Tak, standard gwarantuje, że obiekty zostaną zniszczone w odwrotnej kolejności, w jakiej zostały utworzone. Powodem jest to, że jeden przedmiot może używać innego, a zatem polegać na nim. Rozważać:

struct A { };

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

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

Jeśli amiały zniszczeniu zanim bpotem bbędzie posiadać nieprawidłowy członkowskie odniesienia. Niszcząc obiekty w odwrotnej kolejności, w jakiej powstały, gwarantujemy prawidłowe zniszczenie.

wilhelmtell
źródło
Właściwie nigdy nie uważałem, że ta zasada ma zastosowanie również do kolejności niszczenia elementów zakresu!
yano
6

Tak i tak. Kolejność niszczenia jest zawsze odwrotna do kolejności konstrukcji dla zmiennych składowych.

Chris Jester-Young
źródło