2011-09-24 15 views
36

Małe pytanie dotyczące tworzenia obiektów. Że mam tych dwóch klas:Kolejność wywoływania konstruktorów/destruktorów w dziedziczeniu

struct A{ 
    A(){cout << "A() C-tor" << endl;} 
    ~A(){cout << "~A() D-tor" << endl;} 
}; 

struct B : public A{ 
    B(){cout << "B() C-tor" << endl;} 
    ~B(){cout << "~B() D-tor" << endl;} 

    A a; 
}; 

aw głównym utworzyć instancję B:

int main(){ 
    B b; 
} 

Zauważ, że B wynika z A a także ma pole typu A.

Próbuję obliczyć zasady. Wiem, że podczas konstruowania obiektu najpierw wywołuje się jego konstruktor macierzysty, i na odwrót podczas niszczenia.

Co z polami (A a; w tym przypadku)? Kiedy zostanie utworzone B, kiedy wywoła konstruktor A? Nie zdefiniowałem listy inicjalizacyjnej, czy jest jakaś domyślna lista? A jeśli nie ma domyślnej listy? I to samo pytanie o niszczenie.

+3

Twój przykład może być bardziej wyjaśniający, jeśli twoja wiadomość dla destruktora różni się od twojej wiadomości dla konstruktora. Co też robią 'std :: sort'? – Tom

+0

Ponadto, podczas eksperymentów, porównaj konstrukcję i zniszczenie 'B b',' B * b = new B(); usuń b; 'i' A * a = new b(); delete a; '(Porównaj co się dzieje, gdy używasz słowa' virtual' dla twojego destruktora, np. 'virtual ~ A() {cout <<" A-tor "<< endl;}') – Tom

+0

@Tom, jesteś dobrze. Usuwanie błędów kompilatora. – iammilind

Odpowiedz

67
  • Budowa zawsze zaczyna się od podstawy class. Jeśli istnieje wiele baz class es, konstrukcja zaczyna się od lewej najbardziej podstawy. (notatka boczna: Jeśli istnieje dziedziczenie virtual, to ma ono wyższą preferencję).
  • Następnie pola są skonstruowane. Oni są inicjowane w kolejności ich deklarowanej
  • Wreszcie sama class jest skonstruowany
  • Kolejność destruktora jest dokładnie odwrotna

Niezależnie od listy inicjatora, kolejność połączeń będzie jak to: '

  • class B s konstruktora' s pola

    1. bazowe class A nazwie a (typu class A) zostanie zbudowana
    2. Pochodzące class B „s konstruktor
  • +2

    'W końcu sama klasa jest skonstruowana" - czy mówisz tutaj o ciele konstruktora? – Wolf

    +1

    @Wolf: Zgaduję, że tak. – Ludwik

    +0

    @Wolf. Odnosi się do klasy pochodnej – MSD561

    7

    klas bazowych są zawsze zbudowane przed członkami danych. Elementy danych są konstruowane w kolejności, w jakiej są zadeklarowane w klasie. Ta kolejność nie ma nic wspólnego z listą inicjalizacji. Kiedy element danych jest inicjowany, przejrzy listę inicjalizacyjną dla parametrów i wywoła domyślny konstruktor, jeśli nie będzie pasował. Destruktory dla elementów danych są zawsze wywoływane w odwrotnej kolejności.

    +1

    +1 Zwięzły, ale dobry. Może jakiś cukier typograficzny przyciągnie więcej czytelników. – Wolf

    20

    Zakładając, że nie jest wirtualny/wielokrotne dziedziczenie (który komplikuje trochę), to zasady są proste:

    1. Pamięć obiekt przeznaczono
    2. Konstruktor klas bazowych są wykonywane, kończąc większość pochodzi
    3. inicjalizacja element jest wykonany
    4. przedmiotem staje się prawdziwym przykładem swojej klasie
    5. kod
    6. Konstruktor jest wykonywany

    Należy pamiętać, że do kroku 4 obiekt nie jest jeszcze instancją jego klasy, ponieważ zyskuje ten tytuł dopiero po rozpoczęciu wykonywania konstruktora. Oznacza to, że jeśli wystąpi wyjątek wygenerowany podczas konstruktora elementu, destruktor obiektu nie jest wykonywany, ale tylko już skonstruowane części (np. Członkowie lub klasy bazowe) zostaną zniszczone. Oznacza to także, że jeśli w konstruktorze elementu lub klasy bazowej wywołasz dowolną funkcję obiektu wirtualnego obiektu, wywoływana implementacja będzie podstawową, a nie pochodną. Inną ważną rzeczą do zapamiętania jest to, że członek wymieniony na liście inicjalizacyjnej zostanie skonstruowany w kolejności, w jakiej są zadeklarowani w klasie, a nie w kolejności, w jakiej pojawiają się na liście inicjalizacyjnej (na szczęście wystarczająca liczba najbardziej przyzwoitych kompilatorów wyświetli ostrzeżenie, jeśli umieścisz listę członkowie w innej kolejności niż deklaracja klasy).

    Należy również zauważyć, że nawet jeśli w trakcie wykonywania kodu konstruktora obiektu this już zyskał swoją ostateczną klasy (np w odniesieniu do wirtualnego wysłania) destruktor klasy nie będzie można nazwać chyba konstruktor kończy wykonanie . Dopiero wtedy, gdy konstruktor zakończy wykonywanie, instancja obiektu jest prawdziwym obywatelem pierwszej klasy między instancjami ... zanim ten punkt będzie tylko "instancją wanna-be" (pomimo posiadania odpowiedniej klasy).

    Zniszczenie odbywa się w dokładnej kolejności odwrotnej: najpierw destruktor obiektu jest wykonywany, następnie traci swoją klasę (tj. Od tego punktu obiekt jest uznawany za obiekt bazowy), następnie wszystkie elementy są niszczone w kolejności odwrotnej deklaracji, a na końcu proces niszczenia klasy bazowej jest wykonywany aż do najbardziej abstrakcyjnego rodzica. Jeśli chodzi o konstruktora, jeśli wywołasz dowolną funkcję obiektu wirtualnego obiektu (bezpośrednio lub pośrednio) w destruktorze podstawowym lub pręcie, wykonywana implementacja będzie macierzystą, ponieważ obiekt stracił tytuł klasy po zakończeniu destruktora klasy.

    +0

    'zaczynając od najbardziej abstrakcyjnych' w punkcie 2: czy to prawda? – Wolf

    +0

    @Wolf: Tak, jest to gwarantowane. Jeśli posiadasz klasę 'D', która pochodzi od' B', wtedy podczas tworzenia 'D' najpierw wykonywany jest konstruktor' B' (najbardziej abstrakcyjny), a następnie wykonywany jest konstruktor 'D'. W rzeczywistości obiekt staje się prawdziwym 'D' (w odniesieniu do metod wirtualnych) dopiero po zakończeniu budowy wszystkich baz i wszystkich innych elementów (jest to podchwytliwa kwestia wywoływania wirtualnych metod' D' podczas konstruowania jednego z elementów członkowie lub podczas budowy bazowego pod-obiektu). – 6502

    +0

    To nie jest porządek budowy od podstaw do pochodnych, o które prosiłem, ale myślę, że jest to słowo "abstrakcyjne", które wprowadza w błąd. Jak się dowiedziałem, klasa abstrakcyjna to klasa z przynajmniej czystą wirtualną metodą. Proszę spojrzeć na [ten przykład] (http://coliru.stacked-crooked.com/a/2f5e712f8421d303) – Wolf

    0

    Wyjście ze zmodyfikowanego kodu jest:

    A() C-tor 
    A() C-tor 
    B() C-tor 
    ~B() D-tor 
    ~A() D-tor 
    ~A() D-tor 
    
    3

    Klasa bazowa konstruktor zawsze wykonuje first.so kiedy napisać oświadczenie B b; konstruktor A nazywa się pierwszy, a następnie klasy B constructor.therefore wyjście z konstruktorzy będą w kolejności, jak następuje:

    A() C-tor 
    A() C-tor 
    B() C-tor 
    
    1
    #include<iostream> 
    
    class A 
    { 
        public: 
        A(int n=2): m_i(n) 
        { 
        // std::cout<<"Base Constructed with m_i "<<m_i<<std::endl; 
        } 
        ~A() 
        { 
        // std::cout<<"Base Destructed with m_i"<<m_i<<std::endl; 
        std::cout<<m_i; 
        } 
    
        protected: 
        int m_i; 
    }; 
    
    class B: public A 
    { 
        public: 
        B(int n): m_a1(m_i + 1), m_a2(n) 
        { 
        //std::cout<<"Derived Constructed with m_i "<<m_i<<std::endl; 
        } 
    
        ~B() 
        { 
        // std::cout<<"Derived Destructed with m_i"<<m_i<<std::endl; 
        std::cout<<m_i;//2 
        --m_i; 
        } 
    
        private: 
        A m_a1;//3 
        A m_a2;//5 
    }; 
    
    int main() 
    { 
        { B b(5);} 
        std::cout <<std::endl; 
        return 0; 
    } 
    

    odpowiedź w tym przypadku wynosi 2531. jak Constructo R nazywane są tutaj:

    1. B :: A (int n = 2), konstruktor nazywa
    2. B :: B (5) Konstruktor nazywa
    3. B.m_A1 :: A (3) jest wywoływana
    4. B.m_A2 :: A (5) jest wywoływana

    o tej samej sposób Destruktor nazywa:

    1. B :: ~ B() jest nazywany. tj. m_i = 2, który zmniejsza m_i do 1 w A.
    2. B.m_A2 :: ~ A() jest wywoływana. m_i = 5
    3. B.m_A1 :: ~ Wywoływana jest funkcja(). m_i = 3 4B :: ~ A() jest wywoływana., m_i = 1

    W tym przykładzie, konstrukcja m_A1 & m_A2 ma znaczenia z rzędu kolejnością inicjujący ale ich kolejności zgłoszenia.

    Powiązane problemy