Richtiges Löschen in rekursiven Klassen



  • Hallo, zusammen!

    Ich habe mir in den letzten Tagen einen ziemlich simplen Parser für Konfigurationsdateien geschrieben (weil ich INIs uncool finde). Als ich heute noch rekursive Gruppen hinzufügen wollte, machte sich erstmals eine kleine Verwirrung breit.

    So stelle ich mir das in etwa vor:

    Group1 {
    	Key1 = Value1
    	Key2 = Value2
    
    	SubGroup1 {
    		Key1 = Value3
    
    		SubSubGroup1 {
    			Key1 = Value4
    		}
    
    		Key2 = Value5
    	}
    
    	SubGroup2 {
    		Key1 = Value6
    		Key2 = Value7
    	}
    }
    
    Key1 = Value8
    
    Group2 {
    	Key1 = Value9
    
    	SubGroup1 {
    		Key1 = Value10
    	}
    }
    

    Das Parsen sollte eigentlich schon funktionieren. Das große Problem ist das Löschen.

    Momentan sieht meine Klassenarchitektur so aus:

    class CConfigGroup;
    
    class CPrefsParser
    {
    public:
        CPrefsParser( string strFileName )
        {
            m_strFileName = strFileName;
        }
    
        ~CPrefsParser( void )
        {
            // was hier?
            // m_pMainGroup löschen? oder lieber eine neue Funktion schreiben?
        }
    
        bool Parse( void ); // liest das ganze Zeugs ein
    
    private:
        CConfigGroup *m_pMainGroup; // Die (in der Datei) unsichtbare Hauptgruppe, welche alle in der Datei vorhandenen Gruppen und Schlüsselwerte enthält
        string m_strFileName;
    };
    
    class CConfigGroup
    {
    public:
        CConfigGroup( string strName )
        {
            m_strName = strName;
        }
    
    private:
        CConfigGroup *pChild; // erste nachfolgende Kindgruppe
        CConfigGroup *pPeer; // erste nachfolgende Gruppe auf gleicher Ebene
        string m_strName;
        vector< CKeyValues > m_vecValues; // Vector mit Werten (CKeyValues speichert sie in Form von std::pair<string, string>)
    };
    

    Nun die große Frage: Wie lösche ich das Alles richtig? Soll ich den Code zum Löschen in den Destruktor schreiben (wäre sinnvoll, aber ich will in der Lage sein, einen 'CPrefsParser' bei Bedarf zu löschen, und nicht erst, wenn der Stack in dem eine Instanz der Klasse liegt bereinigt wird. Ich habe so oft davon gelesen, dass das explizite Aufrufen des Destruktors viemieden werden sollte, dass ich lieber nachfrage...) Dieselbe Frage gilt für 'CConfigGroup'. Der einfachste Ansatz wäre eine Löschfunktion für alle pPeers aufzurufen, welche die jeweiligen pChilds löscht (also rekursiv), gehört dies in den Destruktor, welchen ich dann mehr oder weniger (delete) explizit aufrufe?
    Oder tut der ganze Code irgendeinem Profi so in den Augen weh, dass ich am besten alles lösche, überdenke und neuschreibe?
    Sorry, falls dieses Problem trivial erscheint, aber es wäre nicht das erste Mal und auch nicht das zweite Mal, dass ich wegen einem Memoryleak oder unzureichend programmierten Deallokieren bis 3 Uhr morgens den Debugger belästige und im Endeffekt die Hälfte recode.

    Danke im Voraus! 😉


  • Mod

    Da momentan nicht ersichtlich ist, wo hier überhaupt etwas erzeugt wird, das gelöscht werden muss, eine wichtige Faustregel:
    Wenn ein Objekt (oder eine Funktion, was auch immer) etwas erzeugt, dann ist es auch dafür verantwortlich. Wenn etwas, das Verantwortung trägt, zerstört wird, dann zerstört es auch das, wofür es verantwortlich ist. Verantwortung wird niemals übertragen (außer eventuell in genau definierten Fällen, wenn man einen besitzenden Smartpointer oder vergleichbares Objekt erzeugt).

    Auf diese Weise kannst du gar nichts falsch machen.

    Außerdem noch ein nützliches Stichwort:
    Google: RAII



  • Um eine Instanz von CPrefsParser von dir zu einem bestimmten Zeitpunkt zu beenden legst du die Instanz dynamisch an:

    CPrefsParser* instanz = new CPrefsParser(strFileName);
    

    und beendest sie zum gewünschten Zeitpunkt mit

    delete instanz;
    

    NICHT mit dem Destruktor!! Der wird von delete automatisch aufgerufen.

    Vorausgesetzt alle Instanzen von CConfigGroup werden aus der CPrefsParser heraus erzeugt (z.B. in CPrefsParser::Parse() ), so ist die Gesamtheit der CConfigGroup-Instanzen schlicht ein Baum, dessen Wurzel (root) der Pointer in CPrefsParser sein muss.
    Im Destruktor von CPrefsParser sagst du dann schlicht, dass der Baum an der Wurzel "gefällt" werden soll

    CPrefsParser::~CPrefsParser(){
        delete m_pMainGroup; //lösche die Wurzel
    }
    

    Dies ruft dann den CConfigGroup-Destruktor der Wurzel auf. In diesem musst du dann rekursiv vorgehen:

    CConfigGroup::~CConfigGroup(){
    if(pChild != NULL){delete pChild;} //ruft den Destruktor des Kindes auf, der den Destruktor dessen Kindes, usw bis die unterste Ebene erreicht ist
    if(pPeer != NULL){delete pPeer;} //ruft den Destruktor des Peers auf, der zuerst den Destruktor dessen Kindes, dann den dessen Peers, usw bis es keinen weiteren Peer mehr gibt
    
    //mit dem Ende des scopes werden die anderen Member mit ihren Standard-Destruktoren automatisch gelöscht
    }
    


  • PS:
    das Wichtige bei Rekursionen ist, dass die Abbruchbedingung erfüllt werden kann. Beim Anlegen des Baumes musst du also unbedingt darauf achten, dass die "Blätter" des Baumes (die untersten Childs und die letzen Peers) mit ihren jeweiligem Zeiger auf

    NULL
    

    zeigen. D.h. beim Erstellen der CConfigGroups-Instanzen Peer und Child immer mit NULL initialisieren (sollte aber eigentlich klar sein).


Anmelden zum Antworten