double free error bei Mehrfachvererbung



  • Ich bin zwar nicht so QT, aber wxWidgets und das System wird ähnlich sein. Hier wird versucht die gleiche Philosophie wie Java zu fahren. Jedes Objekt wird, egal ob man es auf dem Stack oder Heap anlegt, angelegt, jedoch zeigt das Objekt auf die eigentlichen Daten auf dem Heap. Du kannst also mit Referenzen oder Kopien arbeiten. Über den RefCounter wird überwacht, wieviele Stack oder Heap Objekte es noch gibt, die auf diesen Heap Eintrag zeigen und bei 0 wird das Objekt auf dem Heap zerstört.
    Damit das System ordentlich funktioniert müssen der Destruktor virtuell oder GAR NICHT implementiert sein. Damit der Basisdestruktor nicht verdeckt wird. Mein Vorredner hat das schon gesagt!
    Bevor eine Implementierung leer bleibt, sollte sie entfallen!



  • Hier ein kurzer Ausschnitt aus dem Code:

    Hier erzeuge ich das child-Object, m_subwindows ist dabei ein Array von Pointer auf subwindow (parent-Klasse)

    m_subwindows.push_back(new subwindow_search (&m_window, "Suchen", m_Settings, m_StatusDisplay));
    

    Das Objekt wird mit delete m_subwindows[i] wieder gelöscht.

    hier die Klassendefinitionen

    class subwindow
    {
    
    public:
        subwindow(QWidget** window, string name) : m_window(window), m_is_active(false), m_name(name) {}
        virtual ~subwindow(); // ohne Virtual => Fehler
    ...
    

    (mit dem Schlüsselwort virtual kommt der Fehler nicht) und

    class subwindow_search  : public QObject, public subwindow
    {
    	Q_OBJECT
    
    public:
    	subwindow_search(QWidget** window, string name, settings_reader* Settings, status_display* StatusDisplay) : subwindow(window, name), m_Settings (Settings), m_StatusDisplay (StatusDisplay) {}
    	subwindow_search() : subwindow(NULL, ""){}
    	~subwindow_search();
    ...
    

    subwindow_search nutzt eine Methode von subwindow um einen Button zu erstellen. Der slot dafür liegt aber in der subwindow_search-Klasse.
    Vermutlich liegt da das Problem, dass von QObject den slot löschen will, obwohl der schon gelöscht wurde. Ich nahm an, dass das ähnlich einer Methode ist und da nichts passieren kann.



  • Ulf schrieb:

    Nein, parent hat keinen virtuellen Destruktor. Testweise sind beide Destruktoren von parent und child nicht virtuell aber (momentan) leer.

    Edit: Ohh, wenn ich ihn virtual deklariere, klappt es! Ich nahm an, es sei egal, wenn der parent Destruktor leer wäre. Könnt ihr mir sagen, wieso es dann zu dem Fehler kommt?

    Ich danke dir schonmal für deinen Tipp!

    Das Wort virtuell erlaubt, dass gleiche Methoden der Basisklass(en) nicht verdeckt werden.

    class Obj
    {
    
    };
    
    class Array : public Obj
    {
    Array() : Obj(this)
    {
    }
    
    Array(const Array &a)
    {
      // implementier ich später
      // DONT!!!!!
      // entweder ich schreibe hier die Kopierroutine hin, oder ich kicke den Part,
      // damit der Compiler hier einfach die Kopierroutine einfügt, also Array(...) : vec(a.vec)
    }
    
    ~Array()
    {
      // implementier ich später
      // DONT!!!!!
      // wird mir vererbt überlasse ich es entweder der Basisklasse, wie sie sich zerstört (mit virtual)
      // oder sorge für die Zerstörung!
    }
    
    vector<int> vec;
    
    };
    


  • Ulf schrieb:

    Hier ein kurzer Ausschnitt aus dem Code:

    Hier erzeuge ich das child-Object, m_subwindows ist dabei ein Array von Pointer auf subwindow (parent-Klasse)

    m_subwindows.push_back(new subwindow_search (&m_window, "Suchen", m_Settings, m_StatusDisplay));
    

    Das Objekt wird mit delete m_subwindows[i] wieder gelöscht.

    hier die Klassendefinitionen

    class subwindow
    {
    	
    public:
        subwindow(QWidget** window, string name) : m_window(window), m_is_active(false), m_name(name) {}
        virtual ~subwindow(); // ohne Virtual => Fehler
    ...
    

    (mit dem Schlüsselwort virtual kommt der Fehler nicht) und

    class subwindow_search  : public QObject, public subwindow // sehr hässlich!!!!!!!!!!!!!!
    {
    	Q_OBJECT
    	
    public:
    	subwindow_search(QWidget** window, string name, settings_reader* Settings, status_display* StatusDisplay) : subwindow(window, name), m_Settings (Settings), m_StatusDisplay (StatusDisplay) {}
    	subwindow_search() : subwindow(NULL, ""){}
    	~subwindow_search();
    ...
    

    Die Fehlermeldung kommt aber auch nur, wenn die child-Klasse von QObject erbt.

    Das ist so falsch!

    subwindow ist ein Objekt und dein search subwindow ist ein subwindow!

    class subwindow : public QObject { }
    
    class subwindow_search : public QObject { }
    

    Merke dir in C++ generell:
    Ist deine Klasse dynamisch, also benutzt du new oder malloc etc. musst du einen destruktor einbauen, der es wieder freigibt. Benutzt du sowas wie vector<int> m_vec; kümmert sich vector um die Löschung selber und du implementierst KEINEN Destruktor, somit brauchst du auch keine virtuellen dtors einbauen!



  • PhilippHToner schrieb:

    Ulf schrieb:

    Hier ein kurzer Ausschnitt aus dem Code:

    class subwindow_search  : public QObject, public subwindow // sehr hässlich!!!!!!!!!!!!!!
    {
    	Q_OBJECT
    	
    public:
    	subwindow_search(QWidget** window, string name, settings_reader* Settings, status_display* StatusDisplay) : subwindow(window, name), m_Settings (Settings), m_StatusDisplay (StatusDisplay) {}
    	subwindow_search() : subwindow(NULL, ""){}
    	~subwindow_search();  // hier verdeckst du den Basisklassen destruktor und auch wenn der basisklassen dtor virtuell
            // virtual ~subwindow_search(); wäre richtig!
    ...
    


  • PhilippHToner schrieb:

    Merke dir in C++ generell:
    Ist deine Klasse dynamisch, also benutzt du new oder malloc etc. musst du einen destruktor einbauen, der es wieder freigibt. Benutzt du sowas wie vector<int> m_vec; kümmert sich vector um die Löschung selber und du implementierst KEINEN Destruktor, somit brauchst du auch keine virtuellen dtors einbauen!

    Ich kann dir nicht ganz folgen. Ich habe einen Vektor von Pointern auf subwindow, die ich per new erstelle. Zum Schluss gehe ich den Vektor durch und delete jedes einzelne subwindow. Den Vektor selber lösche ich zum Schluss nicht.

    PhilippHToner schrieb:

    ~subwindow_search();  // hier verdeckst du den Basisklassen destruktor und auch wenn der basisklassen dtor virtuell
    

    Ich habe beides mal ausprobiert, den Dtor einmal mit und einmal ohne virtual definiert. Beide Male wird zunächst der subwindow-Dtor, dann der subwindow_search-Dtor aufgerufen.

    Ist es nicht egal, ob ich den subwindow_search-Dtor virtual definiere, wenn niemand mehr von dieser Klasse erbt? Oder wird der Basisklassen-dtor vllt nicht aufgerufen, wenn ich direkt ein Objekt subwindow_search erstelle?

    Edit:

    Kannst du mir sagen, wieso

    class subwindow_search  : public QObject, public subwindow
    

    hässlich ist?



  • edith: hier stand quäse



  • Ulf schrieb:

    PhilippHToner schrieb:

    Merke dir in C++ generell:
    Ist deine Klasse dynamisch, also benutzt du new oder malloc etc. musst du einen destruktor einbauen, der es wieder freigibt. Benutzt du sowas wie vector<int> m_vec; kümmert sich vector um die Löschung selber und du implementierst KEINEN Destruktor, somit brauchst du auch keine virtuellen dtors einbauen!

    Ich kann dir nicht ganz folgen. Ich habe einen Vektor von Pointern auf subwindow, die ich per new erstelle. Zum Schluss gehe ich den Vektor durch und delete jedes einzelne subwindow. Den Vektor selber lösche ich zum Schluss nicht.

    Ja das ist schon richtig, aber mit new erzeugte Klassen, die von QObject erben brauchst du nicht zu löschen! Man braucht halt noch ein übergeordnete Klasse für Garbage-Collection.
    Zumindest ist das bei wxWidgets so und der Sinn von Reference Countern!

    Ulf schrieb:

    PhilippHToner schrieb:

    ~subwindow_search();  // hier verdeckst du den Basisklassen destruktor und auch wenn der basisklassen dtor virtuell
    

    Ich habe beides mal ausprobiert, den Dtor einmal mit und einmal ohne virtual definiert. Beide Male wird zunächst der subwindow-Dtor, dann der subwindow_search-Dtor aufgerufen.

    Ist es nicht egal, ob ich den subwindow_search-Dtor virtual definiere, wenn niemand mehr von dieser Klasse erbt? Oder wird der Basisklassen-dtor vllt nicht aufgerufen, wenn ich direkt ein Objekt subwindow_search erstelle?

    Es kommt drauf an, wie das Objekt zerstört wird (s. http://www.codersource.net/c/c-miscellaneous/c-virtual-destructors.aspx). Ein "delete" soll beide dtoren aufrufen und nicht nur das des Typen wie der Aufruf es festlegt.
    Wenn das QObject den RefCounter auf 0 hat ruft er delete this; auf. Wenn deine Klasse keinen virtual dtor hat, wird er nicht aufgerufen! Der Dtor bildet mit virtual eine Anomalie im Gegensatz zu den anderen Aufrufkonventionen!

    Ulf schrieb:

    Edit:

    Kannst du mir sagen, wieso

    class subwindow_search  : public QObject, public subwindow
    

    hässlich ist?

    Gegenfrage: Gibt es ein Kind mit zwei biologischen Müttern? Nein also die OO-Hierarchie würde es schon zulassen, aber auch nur, wenn die Klassen disjunkt sind. Wenn die Basisklassen Methoden bzw. sogar weitere Basisklassen vererbt bekommen, die gleich sind, musst du mit virtuellen Basisklassen arbeiten und da kommt spätestens die Zeit, wo man seine Klassendiagramme neu überdenken sollte! Also es ist möglich aber nicht wirklich "nice". Java verbietet es z.B. gleich, damit wir uns nicht den Kopf darüber zerbrechen müssen. Du solltest deine Hierarchie Bottom-Top bauen und deine Bottom-Klassen bekommen QObjects vererbt. Dann kann deine search_subwindow Klasse ein subwindow sein und das wiederum ein QObject. Aber wenn nur dein search_subwindow ein QObject ist, dann kann ich ja gar nicht mit einem subwindow arbeiten?

    Lies dir in jedem Fall nochmal die Dokumentation durch. In wxWidgets wollen alle Objekte einen Pointer auf die Parent-klasse. Wenn du dann z.B. in der GUI das oberste Fenster (Mainframe) beendest, schließt (löscht) es rekursiv seine Kinder (die mit new erzeugt wurden). Du erzeugst also auf dem Heap und musst dann aber gleich den pointer irgendwo anfügen, damit der pointer nicht verschwindet und leaks entstehen.



  • Ich danke dir für deine Erklärungen!



  • Bei Qt übernimmt das Löschen der Parent. Das funktioniert natürlich nur dann wenn einer angegeben ist. Bei den Standardkonstruktoren ist der Parent defaultmäßig auf 0 gesetzt, so dass man den schonmal leicht vergessen kann.
    Das Ganze ändert sich dann, wenn ein QWidget Objekt einem Layout übergeben wird, dann übernimmt das nämlich die Elternschaft und du musst wieder nicht selber löschen.
    Das ganze setzt natürlich voraus, dass man im Konstruktor deiner abgeleiteten Klassen den Konstruktor von QObject (oder vielleicht QWidget) auch aufruft. Das habe ich in den Codeausschnitten hier nicht gesehen.


Anmelden zum Antworten