Dziedziczenie a destruktor

Odpowiedz Nowy wątek
2012-04-19 14:29

Rejestracja: 11 lat temu

Ostatnio: 7 lat temu

0

Witam,
Podczas pracy nad programem napotkałem pewien błąd, który powoduje krytyczny wyjątek programu. Udało mi się go zlokalizować: destruktor klasy.

By lepiej zobrazować problem posłużę się przykładem.


class CRectangle {  //klasa prostokata
  private:
    unsigned lenb;  //dlugosc boku b  

  protected:
     SPoint *pnt;     //punkt poczatkowy prostokatu (gorny lewy rog)
     unsigned lena;  //dlugosc boku a
     (...)
  public:
     (...)
     ~CRectangle() {
       if (pnt != NULL) delete pnt;
     }
     (...)
}

class CSquare : public CRectangle { //klasa kwadratu
    //wszystkie obiekty dziedziczone po prostokacie

  public:
    (...)
    ~CSquare()  {
      if (pnt != NULL) delete pnt; //tutaj pojawia sie problem!
    }
    (...)
}

Powyższy kod skutkuje błędem. Usunięcie destruktora klasy CSquare załatwia problem - wszystko działa bez zarzutu. I stąd moje pytanie: czy jeśli dziedziczę obiekt X z innej klasy to usuwając daną klasę, X jest usuwany korzystając z destruktora klasy bazowej?

Pozostało 580 znaków

2012-04-19 14:37

Rejestracja: 8 lat temu

Ostatnio: 4 miesiące temu

1

Niszczenie obiektu działa analogicznie do tworzenia go, lecz w drugą stronę:
Gdy konstruowany jest obiekt, najpierw wywoływany jest konstruktor klasy bazowej, i dopiero potem klas dziedziczących z niej.
Przy niszczeniu działa to w drugą stronę, dlatego też próba dwukrotnego zwolnienia pamięci kończyła się błędem w Twoim przypadku.

No to fajnie, szczerze powiedziawszy to nie miałem pojęcia, że tak sprytnie jest to zrobione. Dzięki wielkie ;) - robin3d 2012-04-19 14:38

Pozostało 580 znaków

ŁF
2012-04-19 15:42
ŁF
Moderator

Rejestracja: 17 lat temu

Ostatnio: 3 godziny temu

0

btw po zwolnieniu pamięci wskaźnik do niej powinieneś ustawić na null. wtedy nie miałbyś powyższego problemu.


Racja, dzięki - robin3d 2012-04-19 15:43

Pozostało 580 znaków

2012-04-19 17:14

Rejestracja: 8 lat temu

Ostatnio: 1 rok temu

0

Po pierwsze i najważniejsze.

NIGDY:
if (pnt != NULL) { delete pnt; }

ZAWSZE!!!
if (pnt != NULL) { delete pnt; pnt = NULL; }

Po drugie:
Jeśli klasa A dziedziczy z B, przed wywołaniem destruktora A zostaje wywoływany destruktor B.
I w tym momencie wchodzi filozofia OOP. Jeśli jakieś pole należy do klasy bazowej ( prostokąt ), nie należy w nie ingerować z klasy, która dziedziczy.

Współrzędne wierzchołków ( SPoint ) są własnością prostokąta ( czy - ogólniej - czworokąta ). Kwadrat, w momencie "się usuwania" "wie", że prostokąt został już usunięty. Wierzchołki należą do prostokąta ( czy nawet jeszcze dalszej klasy bazowej, z której dziedziczy prostokąt ) - nie powinien więc nawet próbować ich usuwać.

@Aggr3SSIOn kolejność jest odwrotna, niż napisałeś. @autor: można powiedzieć, że jeśli nie definiujesz zmiennej, to nigdy nie tworzysz jej i nie zwalniasz. zawsze powinna zajmować się tym klasa, po której dziedziczysz. - ŁF 2012-04-19 17:32
Dla przyjaciół agresor ;] Istotnie, pochrzaniłem. Ale część o tym, że każdy powinien sprzątać swoje brudy pozostaje prawdziwa ;] - 4ggr35510n 2012-04-19 22:09

Pozostało 580 znaków

2012-04-19 17:45

Rejestracja: 7 lat temu

Ostatnio: 7 lat temu

2

Po pierwsze i najważniejsze: kwadrat dziedziczy po prostokącie??? Naprawdę? Jeżeli ktoś Cię tak nauczył, to zmień nauczyciela. To jakaś masakra. Bazowa klasa figury i dziedziczące po nim konkretne figury. To typowy test na OOP.

Po drugie i może jeszcze ważniejsze: jeżeli dziedziczenie wchodzi w grę to destruktor bazowej virtual.

Pokaż pozostałe 5 komentarzy
@bogdans niezupełnie. Powiedzmy, że masz bazową klasę prostokąt i dwa settery, setWidth oraz setHeight. Jeżeli kwadrat ma być prostokątem, to wtedy setWidth ma wpływ na wysokość, czyli kwadrat nie zachowuje się jak prostokąt. O dziwo ten przykład jest nawet na Wiki przy okazji zasady Liskov. - Zjarek 2012-04-19 23:47
@Zjarek, to czy kwadrat może dziedziczyć po prostokącie zależy od metod jakie są w klasie Prostokat. Ogólniej, dopóki nie mamy pełnej listy metod klasy bazowej, to nie wiemy jakie są dopuszczalne klasy pochodne. Popularny przykład dziedziczenia, to klasa ogólna Figura i różne konkretne figury. Jeżeli w klasie Figura zdefiniujemy metodę skaluj(f) przyjmującą jako argument ciągła funkcję f kąta, która przekształca figurę wg "jednokładności" o zmiennej skali, to ani Prostokat, ani Kolo, ani Romb, ani żadna inna popularna figura nie może dziedziczyć po klasie Figura. - bogdans 2012-04-20 00:27
@bogdans: to że „w życiu” coś jest szczególnym przypadkiem czegoś nie oznacza, że to dobry przykład na OOP. Akurat przypadek kwadrat-prostokąt nie wygląda dobrze ani w jedną, ani w drugą stronę. - Azarien 2012-04-20 08:12
Nie twierdzę, że to jest idealny przykład OOP, ale na pewno nie zasługuje na komentarz typu "kwadrat dziedziczy po prostokącie??? Naprawdę? Jeżeli ktoś Cię tak nauczył, to zmień nauczyciela. To jakaś masakra." Jeżeli klasa Prostokat już istnieje, i pojawiła się potrzeba klasy Kwadrat, to co jest złego w dziedziczeniu Kwadrat(Prostokat) i nadpisaniu w klasie Kwadrat wszystkich metod zmieniających "niezależnie" width i height? - bogdans 2012-04-20 09:14
Po zastanowieniu trochę zmieniłem zdanie. To jest dobry "dydaktycznie" przykład, pokazuje, że pola w klasie powinny być private. - bogdans 2012-04-20 09:40

Pozostało 580 znaków

2012-04-19 23:15

Rejestracja: 12 lat temu

Ostatnio: 15 minut temu

0
4ggr35510n napisał(a):

Po pierwsze i najważniejsze.

NIGDY:
if (pnt != NULL) { delete pnt; }

ZAWSZE!!!
if (pnt != NULL) { delete pnt; pnt = NULL; }

Można pominąć ifa - kasowanie nulla nie powoduje błędu.

Pozostało 580 znaków

2012-04-19 23:25

Rejestracja: 9 lat temu

Ostatnio: 2 lata temu

0

Aktualnie jeszcze lepiej zamiast pnt = NULL; dać pnt = nullptr;


Pozostało 580 znaków

2012-04-20 00:12

Rejestracja: 7 lat temu

Ostatnio: 7 lat temu

0

Jak już zostało zauważone - to miało tylko wskazać pewną złą praktykę.

Ważniejsza kwestia to wirtualny destruktor klasy bazowej.

Nie jestem pewien, ale to chyba absolutna bzdura. Przy wywołaniu destruktora klasy pochodnej, destruktor klasy dziedziczonej zostanie wywołany, niezależnie od tego, czy którykolwiek z nich jest bądź nie jest wirtualny. A tutaj jest problem z dwukrotnym zwalnianiem tej samej pamięci. - 4ggr35510n 2012-04-20 00:24
O architekturze rozwiązania już napisałem, że jest bzdurna. Co do braku konieczność pisania wirtualnego destruktora - sprawdź sobie ;). A potem ludzie narzekają na wycieki... - VeldrinLab 2012-04-20 00:33
CRectangle nie implementuje żadnym metod wirtualnych, nie widzę tu potrzeby dla wirtualnego destruktora. Nie każda hierarchia dziedziczenia musi być polimorficzna... - 4ggr35510n 2012-04-20 01:19
Człowiek zapomina o forum na kilka dni, a tu takie kwiatki. Tak, olejmy wirtualne destruktory. Jesteś całkowicie pewny, że autor nigdy nie będzie planował trzymać sobie np. kolekcji wskaźników klasy bazowej? Grzecznie będzie czyścił i co się stanie? Nic dobrego. Co ciekawe, nie widzimy całego kodu, więc trudno powiedzieć, czy polimorfizm już nie wchodzi w grę;) - VeldrinLab 2012-04-25 23:48

Pozostało 580 znaków

Odpowiedz

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0