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!

-
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 sollCPrefsParser::~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 aufNULLzeigen. D.h. beim Erstellen der CConfigGroups-Instanzen Peer und Child immer mit NULL initialisieren (sollte aber eigentlich klar sein).