virtual



  • Hallo,

    ich hab zwei Fragen zu folgendem Programmcode:

    1.)
    aus welchem Grund ist in folgendem Programmcode der Konstruktor der Klasse ListElem protected und nicht public?

    2.)
    warum ist der Destruktor der Klasse ListElem virtual?

    #include <cstdlib>         // fuer NULL
    #include <new>             // fuer set_new_handler
    #include <iostream>
    using namespace std;
    
    void error(char *msg)
    {
      cerr << msg << endl;     // wird ohne Verzögerung ausgegeben
    }
    
    void out_of_memory()       // wird ausgeführt wenn new fehlschlägt
    {
      error("out of memory");
    }
    
    class ListElem
    {
      protected:
        ListElem *pred, *succ;
        ListElem() : pred(NULL), succ(NULL) {}    // Zeiger pred und succ auf NULL
    
      public:
        virtual ~ListElem() {}
        virtual void print() const = 0;           // soll nur aus abgeleiteten
                                                  // Klassen verwendet werden
      friend class Liste;                         // Liste darf auf Attribute zugr.
    };
    
    class Liste
    {
      private:
        ListElem *head;       
    
      public:
        Liste() : head(NULL) {}     // head wird mit NULL initialisiert
        void insert(ListElem * neu);    
        ListElem *remove();
        void print() const;
        const ListElem *first() const { return head; }
    };
    
    void Liste::insert(ListElem *neu)
    {
       if(head != NULL)               
       {
          head->pred = neu;
          neu->succ = head;
       }
    
       head = neu;
    }
    
    ListElem* Liste::remove()
    {
       ListElem *first = head;
    
       if(head == NULL)
          error("Die Liste ist leer!");
       else
       {
          head = head->succ;
          if(head != NULL)
             head->pred = NULL;
       }
    
       return first;
    }
    
    void Liste::print() const
    {
       ListElem *ptr = head;
    
       if(ptr == NULL)
          error("Die Liste ist leer!");
       else
          while(ptr != NULL)
          {
             ptr->print();
             ptr = ptr->succ;
          }
    
       cout << endl;
    }
    
    class Zahl : public ListElem
    {
      private:
        long wert;
    
      public:
        Zahl(long w = 0) : wert(w) {}
        void print() const   
        {
           cout << wert << ' ';
        }
    }; 
    
    int main()
    {
       set_new_handler(out_of_memory); // o_o_m wird ausgeführt wenn new fehlschlägt
       Liste l;
       long wert;
       cout << "Inhalt der Liste:" << endl;
       l.print();                      // noch leer
       cout << "Ganzzahlige Werte eingeben (0 == Ende):" << endl;
    
       do
       {
          cin >> wert;
          if (wert != 0)
             l.insert(new Zahl(wert));
       } while (wert != 0);
    
       cout << "Inhalt der Liste:" << endl;
       l.print();
       cout << "Liste wird geleert:" << endl;
    
       while (l.first() != NULL)
       {
          delete l.remove();
          l.print();
       }
    
       cin.get(); 
       cin.get();
       return 0;
    }
    


  • Weil es jemand so programmiert hat.



  • zu 1) Würde sagen da ein ListElem sowiso nicht erstellt werden kann ( hat eine pure virtuelle methode ) macht es auch keinen Sinn dass der constructor public ist;
    zu 2) Das macht man eben so damit bei abgeleiteten klassen auch immer der richtige destructor aufgerufen wird.
    Kurt



  • zuerst einmal sorgt

    virtual void print() const = 0;
    

    dafür, dass von der Klasse keine direkte Instanz erzeugt werden kann. Das heißt, es können nur abgeleitete Klassen erzeugt werden.

    Ob der Konstuktor nun public oder protected ist, macht in diesem Fall keinen Unterschied, da Konstruktoren nicht vererbt werden und so dieser Konstuktor so oder so nur von abgeleiteten Klassen aufgerufen werden kann. Privat sein darf er nicht, da sonst abgeleitete Klassen keine Möglichkeit mehr hätten, ihren ListElem-Anteil zu initialisieren.

    Destruktoren sollte bei Basisklassen immer virtuell gemacht werden, der grund dazu:

    Nehmen wir an, di hast eine Basisklasse und eine abgeleitete Klasse:

    class Foo
      {
      int a;
      int b;
      public:
      Foo() : a(4), b(5) {};
      ~Foo() {};
      };
    
    class Bar : public Foo
      {
      largeClass * myPointer;
      public:
      Bar() : myPointer(new largeClass), Foo() {}; 
           //hier wird neue Speicherplatz für ein Objekt von largeClass alloziiert
      ~Bar() { delete myPointer; }; //und hier wieder freigegeben
      };
    

    soweit schön und gut. Jetzt kommt Polymorphie ins spiel:

    Foo * pf; //ein zeiger auf Foo
    pf = new Bar(); //ist okay, Bar ist ja von Foo abgeleitet
                    //das Bar-Objekt hat jetzt seinen largeClass-zeiger
                    //mitsamt speicherplatz
    delete pf; //ARGS
    

    Beim delete wird natürlich der Destruktor von Foo aufgerufen, weil pf ja ein zeiger auf Foo ist. Der Destruktor von Bar wird nicht aufgerufen, und damit vergammelt unser largeClass-Speicherbereich bis zum jüngsten Gericht, ein Musterbeispiel von Speicherleck.
    macht man aber den Destruktor von Foo virtuell, so wird bei "delete pf;" in der vtable des Objektes geguckt und gesehn, dass es ja den Bar-Destruktor gibt. Also kommt das normale Procedere: es werden sowohl von Bar als auch von Foo die Destruktoren aufgerufen, und alle sind glücklich.

    im gcc gibts sogar eine Option, die eine Warnung ausspuckt, wenn man von einer Klasse mit nichtvirtuellem Destruktor ableitet:

    gcc -WeffC++
    

    (bin nicht ganz sicher ob großes oder kleines c)
    Das aktiviert eben jene Warnung und überprüft ein paar weitere Stolpersteine, die in Scott Meyer's "effective C++" aufgeführt sind.


Anmelden zum Antworten