2010-04-07 9 views
5

To pytanie może brzmieć zbyt głupio, jednak nie znajduję konkretnej odpowiedzi gdziekolwiek indziej.Kto nazywa Destruktor klasy, gdy operator delete jest używany w dziedziczeniu wielokrotnym

Przy niewielkiej wiedzy na temat skuteczności późnego wiązania i wirtualnego słowa kluczowego używanego w dziedziczeniu.

Podobnie jak w przykładzie kodu, w przypadku dziedziczenia, w którym do zwolnienia pamięci używany jest wskaźnik klasy bazowej wskazujący obiekt klasy pochodnej utworzony na sterowniku stosu i usunięcia, wówczas destruktor zbioru pochodnego i podstawy będzie wywoływana w kolejności tylko wtedy, gdy destruktor podstawowy jest deklarowaną funkcją wirtualną.

Teraz moje pytanie brzmi:

1) Gdy destructor bazy nie jest wirtualny, dlaczego problem nie nazywając dtor pochodzący nastąpić tylko wtedy, gdy w przypadku użycia „usuń” operator, to dlaczego nie w przypadku, biorąc pod uwagę poniżej:


derived drvd; 
base *bPtr; 
bPtr = &drvd; //DTOR called in proper order when goes out of scope. 

2) When "delete" operator is used, who is reponsible to call the destructor of the class? The operator delete will have an implementation to call the DTOR ? or complier writes some extra stuff ? If the operator has the implementation then how does it looks like , [I need sample code how this would have been implemented].

3) If virtual keyword is used in this example, how does operator delete now know which DTOR to call?

Fundamentaly i want to know who calls the dtor of the class when delete is used.

<h1> Sample Code </h1> 

class base 
{ 
    public: 
    base(){ 
     cout<<"Base CTOR called"<<endl; 
    } 

    virtual ~base(){ 
     cout<<"Base DTOR called"<<endl; 
    } 
}; 

class derived:public base 
{ 
    public: 
     derived(){ 
      cout<<"Derived CTOR called"<<endl; 
     } 

    ~derived(){ 
      cout<<"Derived DTOR called"<<endl; 
    } 
}; 

I'm not sure if this is a duplicate, I couldn't find in search.

int main() { base *bPtr = new derived();

delete bPtr;// only when you explicitly try to delete an object return 0; 

}

+1

Mały punkt, ale twoje pytanie mówi wiele dziedziczenia. To nie jest przykład wielokrotnego dziedziczenia, to po prostu stare dziedzictwo. – Rich

Odpowiedz

1

kompilator generuje cały niezbędny kod, aby zadzwonić do destruktorów w odpowiedniej kolejności, czy będzie to obiekt stosu lub zmienne członek będzie poza zakresem lub przedmiot sterty usunięciem.

+0

@Marcelo: Jeśli wszystko dzieje się w czasie kompilacji, dlaczego ta implementacja jest nazywana późnym wiązaniem? i jak kompilator wie w momencie kompilacji, który DTOR wywołuje? Przepraszam za moją ignorancję, doceń swój wysiłek. – dicaprio

+0

@dicaprio Kompilator podczas kompilacji emituje kod do obsługi destrukcji za pomocą mechanizmu funkcji wirtualnej. Kod jest wykonywany w czasie wykonywania. –

+0

@Neil: bardzo dziękuję za odpowiedź, czy to znaczy, że kompilator pisze dodatkowe rzeczy do wykorzystania mechanizmu wirtualnego, a operator delete po prostu używa adresu w środowisku wykonawczym do wywołania właściwego DTOR? – dicaprio

1
  1. Inicjujesz typ wyprowadzony, gdy wykracza poza zakres, wywołuje destruktor, wirtualny lub nie.
  2. Kompilator wygeneruje kod wywołujący destruktory. Nie wszystko dzieje się podczas kompilacji. Generowanie kodu działa, ale sprawdzanie, jaki jest adres dtor w czasie wykonywania. Pomyśl o przypadku, w którym masz więcej niż jeden typ wyprowadzenia, a robisz usuwanie przy użyciu wskaźnika bazowego.
  3. Destruktor klasy podstawowej musi być wirtualny, aby wywołać polimorficzne wywołanie dtor typu pochodnego.

Jeśli chcesz dowiedzieć się więcej, spróbuj przeładować nowe i usunąć.

+0

@epronk: Dziękuję, zgadzam się z tobą na 1, ale 2 i 3 nadal nie są jasne. To znowu budzi wiele pytań, które dodałem w odpowiedzi na Marcelo poniżej. – dicaprio

+0

base * bPtr w żaden sposób nie przedłuża czasu życia obiektu i wykracza poza zasięg, zanim zrobi to dvd. (gwiazdy idą po lewej stronie) –

+0

Dobra uwaga !! Twoja sugestia, aby przeciążyć nowego operatora lub usunąć go, jednak moim zdaniem nie wdrażamy niczego, aby wywołać dtor klasy, prawda? – dicaprio

2
  1. Wynika to z faktu, że tha w tym przypadku kompilator wiedzieć wszystko o przedmiot, który ma być zniszczona, która w tym przypadku jest drvd i jest typu derived. Gdy drvd wykracza poza zakres, kompilator wstawia kod, aby wywołać jego destruktor. Jest to słowo kluczowe dla kompilatora. Gdy kompilator zobaczy usuwanie, wstawi kod, aby wywołać destruktor i kod, aby wywołać operator delete, aby zwolnić pamięć. Należy pamiętać, że keyword delete i operater delete są różne.

  2. Gdy kompilator zobaczy, że keyword delete jest używany do wskaźnika, musi wygenerować kod dla jego właściwego zniszczenia. W tym celu musi znać informacje o typie wskaźnika. Kompilator wie o wskaźniku tylko typ wskaźnika, a nie typ obiektu, na który wskazuje wskaźnik. Obiekt, do którego wskazuje wskaźnik, może być klasą podstawową lub klasą pochodną.W niektórych przypadkach, rodzaj obiektu może być bardzo jasno określone na przykład

void fun() 
{ 
    Base *base= new Derived(); 
    delete base; 
} 

Ale w większości przypadków tak nie jest, na przykład ten

void deallocate(Base *base) 
{ 
    delete base; 
} 

So kompilator nie wie, który destruktor wywołuje bazę lub pochodną. W ten sposób działa

  1. Jeśli klasa Base nie ma funkcji wirtualnej (funkcja lub destruktor). Bezpośrednio zapobiega kodowi thr wywołującemu destruktor klasy podstawowej
  2. Jeśli klasa Base ma funkcje wirtualne, wówczas kompilator pobiera informację o destruktorze z vtable.
    1. Jeśli destruktor nie jest wirtualny. Vtable będzie miał adres destruktora bazy i to, co zostanie wywołane. To nie jest w porządku, ponieważ nie jest tu wywoływany właściwy destruktor. To dlatego zawsze zalecane jest, aby zadeklarować destructer klasy bazowej jako wirtualny
    2. Jeśli destructer jest wirtualny vtable będzie miał Correcte adres destructer i kompilator wstawi odpowiedni kod tam
2

+1 Dobre pytanie BTW.

Zobacz, jak działa mechanizm wirtualny dla metody nie destruktora, a znajdziesz destruktor zachowując się inaczej.

W grze są 2 mechanizmy, które mogą nieco pomieszać wydanie . Po pierwsze, mechanizm, który nie jest wirtualny, dzieje się podczas budowy i niszczenia obiektu. Obiekt jest konstruowany z klasy bazowej na klasę pochodną, ​​w tej kolejności, a po zniszczeniu kolejność destruktora jest odwrotna, tak wyprowadzona do klasy bazowej. Nic nowego tutaj.

Zastanów się nad wywołaniem metody nie-wirtualnej na opartym wskaźniku klasy na obiekcie klasy pochodnej, co się dzieje? Implementacja klasy bazowej jest wywoływana. Teraz rozważ wywołanie metody wirtualnej od wskaźnika klasy bazowej do pochodnego obiektu klasy, co się dzieje? Pochodna wersja metody jest nazywana. Nic, czego jeszcze nie wiesz.

Przyjrzyjmy się teraz scenariuszowi destruktora. Wywołanie delete na wskaźniku klasy bazowej do pochodnego obiektu klasy, który ma nie wirtualny destruktor. Wywołany jest destruktor klasy podstawowej, a jeśli klasa bazowa została wyprowadzona z innej klasy, wówczas destruktor zostanie wywołany jako następny. Ponieważ mechanizm wirtualny nie jest w grze, wyprowadzony destruktor nie zostanie wywołany, ponieważ destrukcja rozpoczyna się od destruktora, który wywołujesz w hierarchii i działa w dół do klasy bazowej.

Teraz należy rozważyć przypadek wirtualnego destruktora. delete jest wywoływane na bazowym wskaźniku klasy do pochodnego obiektu klasy. Co się dzieje, gdy wywołujesz dowolną metodę wirtualną na wskaźniku klasy bazowej? Wersja pochodna zostaje wywołana. Tak nazywany jest nasz destruktor klasy pochodnej. Co dzieje się podczas destrukcji, obiekt niszczy od wyprowadzonego destruktora do klasy bazowej, ale tym razem rozpoczęliśmy destrukcje na pochodnym poziomie klasowym z powodu mechanizmu mechanizmu wirtualnego.

Dlaczego obiekt stosu z nie wirtualnym lub wirtualnym destruktorem ulega destrukcji z wywodzącej się do klasy bazowej, gdy wykracza poza zakres?Ponieważ destruktor zadeklarowanej klasy jest w tym przypadku wywoływany, a mechanizm wirtualny nie ma z tym nic wspólnego.

Powiązane problemy