Klassen - Ableitungen und Delete Operator Frage



  • Hallo zusammen.

    Angenommen man hat 3 Klassen:

    Class A
    {
       virtual ~A()
          {
          }
       virtual void a()
          {
          }
    };
    
    Class B
    {
       virtual ~B()
          {
          }
       virtual void b()
          {
          }
    };
    
    Class C : public A, public B
    {
       void a()
          {
          }
       void b()
          {
          }
    };
    

    So, wenn ich jetzt die Klasse C instanziiere und anschließen auf A* oder B* caste, darf ich dann einen delete auf diese Pointer durchführen? Hier der Code:

    int main()
    {
       C* pC = new C();
       A* pA = static_cast<A*>(pC);
       B* pB = static_cast<B*>(pC);
       return 0;
    }
    

    ...dann zeigt pA in dieser Konstellation auf die gleiche Adresse wie pC während pB um sizeof(A) (in diesem Fall 4 Byte) größer ist. Jetzt endlich zu meiner Frage: ist ein

    delete pB;
    

    zulässig?

    Mit Visual Studio 2005 funktioniert das (debug und release) - d.h. ich kann delete auf pA, pB oder pC ohne Exception durchführen - aber die Frage ist jetzt ob das auch "legal" ist.

    Danke für Eure Hilfe!

    Gruß

    Mark


  • Mod

    Das ist immer dann und nur dann legal, wenn - wie in deinem Falle - der statische Typ des Objekts, auf das dein Pointer zeigt (also der Pointeetyp des Pointers) einen virtuellen Destruktor besitzt (und du Zugriffsrechte auf diesen Destruktor hast) oder wenn statischer und dynamischer Typ des Objekts zusammenfallen (wir also keinen Pointer auf eine Basisklasse haben).



  • Hi Camper,

    tut mir Leid aber ich kann Deiner Antwort nicht ganz folgen.
    Also der Destruktor der Klassen A und B sind virtual und dass das eine Voraussetzung ist, ist klar (sonst kann ja der Polymorphismus ja nicht funktionieren)

    Worauf ich quasi hinaus will: darf ich ich den delete operator auf pA, pB oder pC frei wählen (solange ich es natürlich nur einmal mache), auch wenn pB nicht auf die gleiche Adresse zeigt wie pC (und pA).

    Diesen Teil verstehe ich auch nicht ganz:

    ...oder wenn statischer und dynamischer Typ des Objekts zusammenfallen (wir also keinen Pointer auf eine Basisklasse haben).

    Danke!

    Mark



  • Das ist immer dann und nur dann legal, wenn - wie in deinem Falle - der statische Typ des Objekts, auf das dein Pointer zeigt (also der Pointeetyp des Pointers) einen virtuellen Destruktor besitzt (und du Zugriffsrechte auf diesen Destruktor hast)

    Also der Destruktor der Klassen A und B sind virtual und dass das eine Voraussetzung ist, ist klar (sonst kann ja der Polymorphismus ja nicht funktionieren)

    Polymorphismus kann auch ohne virtuellen Destruktor funktionieren.
    Durch die Deklaration des Destruktors als virtual erreichst du gerade das, was du dir wünschst: folgende drei Anweisungen tun im Prinzip das selbe:

    A* pA = new C;
    B* pB = new C;
    C* pC = new C;
    delete pA; // Fall 1, destruktor von A ist als virtual deklariert und public
    delete pB; // Fall 1, destruktor von B ist als virtual deklariert und public
    delete pC; // Fall 1 und 2, destruktor von C ist als virtual deklariert und public,
               // außerdem ist der dynamische Typ von *pC C und der statische auch C, sie stimmen als überein
    

    Dies ist nur genau deshalb so, weil du den destruktor als virtual deklariert hast.

    ...oder wenn statischer und dynamischer Typ des Objekts zusammenfallen (wir also keinen Pointer auf eine Basisklasse haben).

    Ich nenne es mal Fall 2.

    class D
    {
    public:
        ~D();
    };
    class E : public D
    {
    public:
        ~E();
    };
    
    void foo()
    {
        E* e = new E;
        delete e; // Fall 2, der dynamische Typ von *e ist class E, ebenso der statische Typ, außerdem ist der dtor public
        D* d = new E;
        delete d; // illegal (der dynamische Typ von *d ist class E und stimmt nicht mit dem
                  // statischen Typ class D überein und der destruktor von D ist nicht als virtual deklariert
    }
    

    Die Unterscheidung zwischen statischen Typ und dynamischen Typ besteht darin, dass der statische Typ der ist, der angegeben ist (z.b. die Basisklasse) und der dynamische Typ, der sich tatsächlich dahinter verbirgendende Typ, den man über typeid bestimmen könnte.

    Vielleicht kann camper noch was hinzufügen.

    MfG
    DDR-RAM



  • Okay was ich verstehen kann 🙂 ist, dass der Destruktor von A und B jeweils auf den Destruktor von C zeigen (über die vftable, wg. virtual).
    Aber:
    Das Laufzeitsystem merkt sich doch irgendwie für welche Adresse er wieviel Speicher allokiert hat.

    Wenn ich also auf pB (der ja um sizeof(A) von pC/pA versetzt ist) den delete Operator anwende, woher weiß dann das Laufzeitsystem (oder macht das der Compiler?) dass er
    - 4 Byte zurückgehen muss (denn (pB-1) == pC == pA)
    - sizeof(C) Bytes freischaufeln darf (wenn er natürlich nur weiß, für welche Adresse er wieviele Bytes allokiert hat, dann kann dieser Punkt übersprungen werden)

    Gruß,

    Mark

    (der dachte, dass er ein bisschen Ahnung von CPP hätte 🙄 )



  • pub00515 schrieb:

    Das Laufzeitsystem merkt sich doch irgendwie für welche Adresse er wieviel Speicher allokiert hat.

    korrekt!

    Wenn ich also auf pB (der ja um sizeof(A) von pC/pA versetzt ist) den delete Operator anwende, woher weiß dann das Laufzeitsystem (oder macht das der Compiler?) dass er
    - 4 Byte zurückgehen muss (denn (pB-1) == pC == pA)

    Also, das ist natürlich implementationsabhängig aber das einfachste ist
    da, C in der Regel zwei vtable's hat (eine für A und eine für B), im überladenen deleting_destruktor für B, den this ptr anzupassen und dann den deleting_destruktor für C aufzurufen.

    - sizeof(C) Bytes freischaufeln darf (wenn er natürlich nur weiß, für welche Adresse er wieviele Bytes allokiert hat, dann kann dieser Punkt übersprungen werden)

    es wird der deleting_destruktor für C aufgerufen, der C::~C() aufruft und dann operator delete(this) aufruft, wobei this tatsächlich auf C zeigt.

    Hoffe war etwas verständlich. Ist aber auch nicht wirklich wichtig, wie der Compiler es implementiert. Wenn man weiß, wie es sein sollte.

    MfG
    DDR-RAM


Anmelden zum Antworten