Garbage Collection
-
Hallo allerseits!
Aufgrund eines auftretenden Memory Leaks, angezeigt durch eine
terminate called after throwing an instance of 'std::bad_alloc' what(): St9bad_allocexception beschäftige ich mich im Moment ein wenig mehr mit dem Thema Garbage Collection. Was ich leider lernen musste ist, dass C++ keine solche bestizt.
//Eine Klasse class MyObject { [...] int variable; [...] }; //externe variablen MyObject obj; MyObject *objPointer; //Eine Funktion function foo() { //Setzte Variable eines externen Objects obj.variable = 1; //Initialisiere einen EXTERNEN pointer objPointer = new MyObject(); //Initialisiere einen INTERNEN pointer MyObject *anotherObjPointer = new MyObject(); }Sagen wir nun ich habe irgendwo einen loop, in dem ich die Funktion foo() mehrmals aufrufe. Dazu nun zwei Fragen:
-
Der externe Pointer wird jedes Mal neu initialisiert, sodass ich ihn am besten am Ende der funktion auf NULL resetten sollte!?
-
Der internet Pointer wird jedes Mal neu initialisiert, aber auch nur dort deklariert. Muss ich diesen trotzdem am Ende der Funktion mit delete manuell löschen? Ich hatte bisher immer angenommen (und gehofft) dass hier eine automatische Gargbage collection diesen Pointer löscht.
Danke euch im vorraus!
-
-
Man benutzt in C++ so selten wie möglich new! Und wenn, dann nur in Kombination mit SmartPointern. Die räumen auch auf, dh. es gibt keinen Bedarf für einen GC.
Brauchst du ein Array, nimm std::vector statt new[]. Der räumt auf.
Wenn du wirklich dynamische Lebenszeit benötigst, dann nim new in Kombination mit std::unique_ptr oder std::shared_ptr.
Wenn die Lebenszeit begrenzt ist, auf jeden Fall auf Pointer verzichten und normal anlegen.
-
Hallo HeXor,
willkommen im C++-Forum.
Ich nehme, an dass Du bisher Java programmiert hast. C++ und Java unterscheiden sich, was die Anwendung von new betrifft, wesentlich. In C++ gilt die Regel: machst Du keine Carbage, so brauchst Du auch keine Carbage Collection.
Das belegen von externen - und damit globalen - Variablen, zumal in der von Dir gezeigten Weise, ist in C++ eher unüblich. Statt dessen benutzt man in diesen Fällen eher das Singleton Pattern.
Im dritten Fall würde man kein new machen sondern schlicht//Initialisiere einen INTERNEN Objekts MyObject anotherObj; }Möchte man aus anderen Gründen doch einen Pointer benutzen, so packe man diesen in einen Smart-Pointer. Siehe dazu boost::scoped_ptr oder std::unique_ptr. Bei Pointern, die über mehrere Objekte 'herumgereicht' werden, wird der std::shared_ptr verwendet. Die std::Smartpointer sind erst mit C++11 verfügbar - falls Dein Compiler das (noch) nicht kann, hilft boost.
Gruß
Werner
-
HeXor schrieb:
- Der externe Pointer wird jedes Mal neu initialisiert, sodass ich ihn am besten am Ende der funktion auf NULL resetten sollte!?
Das Setzen eines Pointers auf NULL resettet nichts. Du hast dann im konkreten Fall nur ein Speicherleck in Form eines Objektes, auf das nichts mehr zeigt.
-
Ich habe mittlerweile ein relativ komplexes Framework, welches Datensätze einliest und dann über diese looped und verarbeitet.
Daher habe ich einige Externe Variablen und Container, welche für jeden Datensatz benötigt werden. So zum beispiel auch vektoren, welche für jeden Datensatz gefüllt udn verarbeitet werden und mit clear() resettet werden bevor der nächste Datensatz verarbeitet wird. Leider ist die GarbageCollection dabei etwas auf der Strecke geblieben.
Das ganze komplett umzustellen, dafür fehlt mir momentan leider die Zeit, weshalb ich nun nach einer temporären Lösung suche, in der ich meine Objekte etwas aufräume.
*Lies Datensatz ein *fülle externe Vektoren (push_back) *bearbeite Datensatz *schreibe Ergebnis aus *resette externe Vektoren (clear)
-
Also statt einen Pointer zu verwenden
MyObject *obj = new MyObject(param1, param2)sollte man lieber direkt Objekte deklarieren in der Art:
MyObject obj(param1, param2)oder eben am Ende selbst aufräumen:
MyObject *obj = new MyObject(param1, param2) [...] delete obj
-
HeXor schrieb:
Also statt einen Pointer zu verwenden
MyObject *obj = new MyObject(param1, param2)sollte man lieber direkt Objekte deklarieren in der Art:
MyObject obj(param1, param2)oder eben am Ende selbst aufräumen:
MyObject *obj = new MyObject(param1, param2) [...] delete objWo ist denn der Unterschied zwischen 1 und 3, außer dass 1 falsch ist, da das delete vergessen wurde?
Um die Frage zu beantworten: Wenn du keine Gründe hast, die dagegen sprechen, dann nimm 2. Wenn du sehr gut begründen kannst, wieso du ein einzelnes Objekt dynamisch anlegen musst (das kann bei gewissen Polymorphiekonstrukten und sehr langlebigen Objekten der Fall sein), dann nimm weder 1 noch 3, denn wie du an dem falschen 1 siehst, kann man bei 3 leicht etwas falsch machen. Und wenn du auch noch Exceptions und andere unerwartete Sprünge in Erwägung ziehst, wird es schnell unmöglich, es richtig zu machen. Narrensichere Alternativen wurden dir in diesem Thread schon haufenweise genannt.
Und falls du mal in die Verlegenheit kommst, dass du mehrere Objekte dynamisch anlegen musst (was sehr oft der Fall ist), dann ist die Antwort niemals new[], denn new[] ist bloß std::vector ohne dessen Narrensicherheit und ohne sonstige Vorteile.
-
Wenn du etwas auf den Heap legst, muss es da wieder weggeschafft werden, wenn du es nicht mehr brauchst, darauf läuft es hinaus. In den meisten Fällen ist es nicht notwendig, Dinge auf den Heap zu legen, d.h.
MyObject obj(param1, param2)ist in den meisten Fällen der einfachste und beste Weg. Wenn etwas seinen Stapelrahmen überleben soll, zu groß für den Stack ist oder aus anderen Gründen auf den Heap muss, solltest du es per RAII irgendwo verankern. In den meisten Fällen ist der einfachste Weg dafür, einen Smart-Pointer zu verwenden, etwa
std::unique_ptr<MyObject> ptr(new MyObject(param1, param2));oder
std::shared_ptr<MyObject> ptr(new MyObject(param1, param2));bei älteren Compilern wahlweise auch std::tr1::foo_ptr oder boost::foo_ptr.
Ein unique_ptr räumt das Objekt, auf das er zeigt, auf, wenn er seinen Gültigkeitsbereich verlässt (also wenn er selbst zerstört wird), shared_ptr ist eine der-letzte-macht-das-Licht-aus-Konstruktion, d.h. wenn mehrere shared_ptr auf das selbe Objekt zeigen, wird es zerstört, wenn der letzte shared_ptr zerstört wird.
Es ist nicht empfehlenswert, die Aufräumarbeiten immer von Hand zu machen, weil es leicht ist, so was zu vergessen - ganz besonders im Zusammenhang mit Exceptions.
-
Naja nehmen wir mal folgendes Minimalbeispiel:
Eine MyClass.hpp file:
Class MyClass { public: //ein Objekt MyObject obj; //ein Pointer MyObject *objPointer; //eine Funktion void foo(); };Eine MyClass.cpp file:
void MyClass::foo() { MyObject tmp(paramt1, param2); obj = tmp; objPointer = new MyObject(param1, param2); [...verarbeitung...] delete objPointer; }Das obj Objekt muss ich ja erst mit tmp initialisieren (oder gibt es hier einen anderen weg?), während ich den Pointer direkt initialisieren kann (aber dann am Ende löschen muss).
Ich verstehe eure Vorschläge, die, wenn ich mein Framework neu aufbauen würde, eine saubere, "narrensichere" Lösung darstellen würden. Da das Framework aber schon relativ groß ist, versuche ich nun einfach erstmal zu verstehen, wie soetwas prinzipiell (einfach) zu lösen ist.
Ist das Minimalbeispiel soweit ok?
-
obj = MyObject(param1, param2);
-
Wieso ist eine Variable die am Anfang der Funktion MyClass::foo einen sinnvollen Wert bekommt und an deren Ende keinen brauchbaren Wert mehr hat ein Member deiner Klasse? Tut mir leid, das Beispieldesign ist so schlecht, da kann ich nicht einmal raten, was du damit zeigen möchtest. Und somit auch keinen Tipp geben, wie es besser werden kann. Jedenfalls ist es so völlig unbrauchbar.
-
Wenn du das Objekt am Ende der Methode wieder zerstörst, warum willst du es dann in eine Membervariable stopfen?
-
HeXor schrieb:
Aufgrund eines auftretenden Memory Leaks [...] beschäftige ich mich im Moment ein wenig mehr mit dem Thema Garbage Collection. Was ich leider lernen musste ist, dass C++ keine solche bestizt.
C++ bietet andere Mittel und Wege, die es Dir leicht machen, erst gar keinen "Garbage" zu produzieren, der dann weggeräumt werden müsste.
HeXor schrieb:
//Eine Klasse class MyObject { [...] int variable; [...] }; //externe variablen MyObject obj; MyObject *objPointer; //Eine Funktion function foo() { //Setzte Variable eines externen Objects obj.variable = 1; //Initialisiere einen EXTERNEN pointer objPointer = new MyObject(); //Initialisiere einen INTERNEN pointer MyObject *anotherObjPointer = new MyObject(); }Sagen wir nun ich habe irgendwo einen loop, in dem ich die Funktion foo() mehrmals aufrufe.
Ich sehe viel "new" und kein "delete". Wenn Die Funktion oft aufgerufen wird, "leakst" du aufgrund beider "new"s, weil es zu keinem das passende delete irendwo gibt.
Denk doch erstmal genau darüber nach, ob Du new überhaupt brauchst. C++ zwingt Dir die Indirektion per Zeiger gar nicht auf. So etwas wie
void foo() { Klasse* ptr = new Klasse(...); ... delete ptr; }ist im Prinzip schon falsch, weil Du hier den Freispeicher bemühst, wo auch der automatische Speicher gereicht hätte:
void foo() { Klasse obj (...); ... // nix delete, und das ist auch gut so }Selbst mit dem delete da oben kann noch einiges schiefgehen, z.B. wenn zwischendurch eine Ausnahme fliegt. Das Problem dabei ist, dass der rohe Zeiger nicht die richtige Semantik hat. Er fühlt sich nicht für das, auf das er zeigt, verantwortlich ist.
-
pyhax schrieb:
obj = MyObject(param1, param2);Genau das hatte ich eben schon probiert:
in *.hpp
fastjet::ClusterSequence _cluster;in *.cpp
_cluster = fastjet::ClusterSequence(_pseudoJets, _jetDef);Fehlermeldung
FastjetTool.cpp:81: error: no match for ‘operator=’ in ‘((FastjetTool*)this)->FastjetTool::_cluster = fastjet::ClusterSequence(((const std::vector<fastjet::PseudoJet, std::allocator<fastjet::PseudoJet> >&) ((const std::vector<fastjet::PseudoJet, std::allocator<fastjet::PseudoJet> >*)(&((FastjetTool*)this)->FastjetTool::_pseudoJets))), ((const fastjet::JetDefinition&)((const fastjet::JetDefinition*)(&((FastjetTool*)this)->FastjetTool::_jetDef))), ((const bool&)((const bool*)(& false))))’ include/fastjet/ClusterSequence.hh:66: note: candidates are: fastjet::ClusterSequence& fastjet::ClusterSequence::operator=(fastjet::ClusterSequence&)SeppJ schrieb:
Wieso ist eine Variable die am Anfang der Funktion MyClass::foo einen sinnvollen Wert bekommt und an deren Ende keinen brauchbaren Wert mehr hat ein Member deiner Klasse? Tut mir leid, das Beispieldesign ist so schlecht, da kann ich nicht einmal raten, was du damit zeigen möchtest. Und somit auch keinen Tipp geben, wie es besser werden kann. Jedenfalls ist es so völlig unbrauchbar.Ja das hatte ich auch eben gesehen, entschuldigung (und trotzdem danke für die nette reaktion...). Vielleicht ist es so besser:
Eine MyClass.hpp file:
Class MyClass { public: //ein Objekt MyObject obj; //ein Pointer MyObject *objPointer; //eine Funktion void foo(); void finalize(); };Eine MyClass.cpp file:
void MyClass::foo() { MyObject tmp(paramt1, param2); obj = tmp; objPointer = new MyObject(param1, param2); [...verarbeitung...] } void MyClass::finalize() { delete objPointer; }Die main
//ohne params und bla, einfach nur main funktion halt main() { MyClass bar; bar.foo(); bar.finalize(); }Der Pointer soll eben dann noch in vielen anderen Funktionen benutzt werden können. Und eigentlich müsste ich ja dann korrekter weise (auch wenn dann in diesem Beispiel das Ende der Programms erreicht ist) den Pointer am Ende mit finalize() aufräumen.
-
HeXor schrieb:
in *.hpp
fastjet::ClusterSequence _cluster;Globale Variable im Header? Bitte nicht.
Davon abgesehen hast du uns nicht den Code gezeigt, der den Fehler erzeugt, sondern nur einen Teil davon. Ohne compilierfähiges minimales Beispiel kann man dir da nicht so recht helfen.Davon abgesehen: Was möchtest du erreichen?
-
Hast du selber einen operator= überladen? Falls ja, dann achte mal auf const-correctness.
-
pumuckl schrieb:
HeXor schrieb:
in *.hpp
fastjet::ClusterSequence _cluster;Globale Variable im Header? Bitte nicht.
Davon abgesehen hast du uns nicht den Code gezeigt, der den Fehler erzeugt, sondern nur einen Teil davon. Ohne compilierfähiges minimales Beispiel kann man dir da nicht so recht helfen.Davon abgesehen: Was möchtest du erreichen?
Das ist eine Member Variable meiner Klasse in dessen Header file, also eingebettet in
class MyTool { public: fastjet::ClusterSequence _cluster; }
-
HeXor schrieb:
Das ist eine Member Variable meiner Klasse in dessen Header file, also eingebettet in
Merkst was? Anhand deiner bisherigen Codefetzen kann man nichtmal annähernd erahnen, was du wirklich machst, geschweige denn dass man die Ursache des Fehlers erkennt.
pumuckl schrieb:
Ohne compilierfähiges minimales Beispiel kann man dir da nicht so recht helfen.
Davon abgesehen: Was möchtest du erreichen?
-
pumuckl schrieb:
HeXor schrieb:
Das ist eine Member Variable meiner Klasse in dessen Header file, also eingebettet in
Merkst was? Anhand deiner bisherigen Codefetzen kann man nichtmal annähernd erahnen, was du wirklich machst, geschweige denn dass man die Ursache des Fehlers erkennt.
pumuckl schrieb:
Ohne compilierfähiges minimales Beispiel kann man dir da nicht so recht helfen.
Davon abgesehen: Was möchtest du erreichen?
Ich hatte in den vorherigen Beispielen sehr genau klar gemacht was ich wollte, eine Member Variable eienr Klasse initialisieren (siehe meine vorherigen posts) sodass ich dachte jetzt den ganzen kram drum herum weg lassen zu können...
Ein Minimalbeispiel habe ich soweit ich konnte gegeben, den eigentlichen Fehler in einem Minimalbeispiel zu reproduzieren ist schwierig, da ich hier einige Klassen importiere (fastjet klasse) mit weiteren abhängigkeiten, da sich dieses wie gesagt in einem größeren Framework befindet.
Ich habe versucht diese Implementierung:
Deklaration als Membervariable in *.hpp: fastjet::ClusterSquenece *_cluster; Implementation_1 in Funktion der Klasse in *.cpp: _cluster = new fastjet::ClusterSequence(_pseudoJets, _jetDef);Auf eine OHNE Pointer umzubauen:
Deklaration als Membervariable in *.hpp: fastjet::ClusterSquenece _cluster; Implementation_1 in Funktion der Klasse in *.cpp: _cluster = fastjet::ClusterSequence(_pseudoJets, _jetDef);Und bekomme die obige Fehlermeldung. Folgendes funktioniert:
Deklaration als Membervariable in *.hpp: fastjet::ClusterSquenece _cluster; Implementation_1 in Funktion der Klasse in *.cpp: fastjet::ClusterSequence tmp(_pseudoJets, _jetDef); _cluster = tmp;Hoffe das ist verständlich, wenn nicht lasst es mich wissen...
-
Aus der Fehlermeldung lese ich, dass du einen
ClusterSequence& operator=(ClusterSequence&);in der Klasse fastjet::ClusterSequence deklariert hast. Temporäre Objekte lassen sich nicht an non-const-Referenzen binden, und der benutzerdefinierte operator= verhindert die Generation eines Default-Operators.
Was du eigentlich willst, ist ein
ClusterSequence& operator=(ClusterSequence const &);Dass Objekte in C++ keine Referenztypen sind, ist dir bekannt?
Ich vermute aber, dass das grundlegende Problem tiefer geht und du eigentlich keine Funktionen foo und finalize willst, sondern Konstruktoren und Destruktoren. "finalize" ist in C++ kein spezieller Funktionsname wie in Java, und das Hantieren mit halbfertigen Objekten ist problematisch - letzteres gilt nicht nur für C++.
Kann es sein, dass du eigentlich
class MyClass { public: MyClass(foo_t const &pseudo_jets, bar_t const &jet_defs); ~MyClass(); private: fastjet::ClusterSequence cluster_; }; MyClass:MyClass(foo_t const &pseudo_jets, bar_t const &jet_defs) : cluster_(pseudo_jets, jet_defs) { } MyClass::~MyClass() { // Inhalt von "finalize" stattdessen hier }willst, um
MyClass obj(pseudo_jets, jet_defs);schreiben zu können? Manuelle Aufräumarbeiten sind dann nicht mehr notwendig, weil
{ MyClass obj(foo, bar); ... } // MyClass::~MyClass() hier ausgeführt und der von obj belegte Speicher freigegeben wird.
-
HeXor schrieb:
Fehlermeldung
FastjetTool.cpp:81: error: no match for ‘operator=’ in ‘((FastjetTool*)this)->FastjetTool::_cluster = fastjet::ClusterSequence(((const std::vector<fastjet::PseudoJet, std::allocator<fastjet::PseudoJet> >&) ((const std::vector<fastjet::PseudoJet, std::allocator<fastjet::PseudoJet> >*)(&((FastjetTool*)this)->FastjetTool::_pseudoJets))), ((const fastjet::JetDefinition&)((const fastjet::JetDefinition*)(&((FastjetTool*)this)->FastjetTool::_jetDef))), ((const bool&)((const bool*)(& false))))’ include/fastjet/ClusterSequence.hh:66: note: candidates are: fastjet::ClusterSequence& fastjet::ClusterSequence::operator=(fastjet::ClusterSequence&)Der Compiler sagt dir, dass dein Kopier-Konstruktor die falsche Signatur hat. Du musst den Parameter als const kennzeichnen. Die übliche Signatur sieht daher so aus.
T& operator = (T const&)Damit gibts du dem Aufrufer die Garantie, dass du das Objekt nicht veränderst. In Kombination mit const-Memberfunktionen, die du vermutlich noch nicht kennst, ist das sehr sinnvoll.
HeXor schrieb:
Ja das hatte ich auch eben gesehen, entschuldigung (und trotzdem danke für die nette reaktion...). Vielleicht ist es so besser:
Eine MyClass.hpp file:
Class MyClass { public: //ein Objekt MyObject obj; //ein Pointer MyObject *objPointer; //eine Funktion void foo(); void finalize(); };Eine MyClass.cpp file:
void MyClass::foo() { MyObject tmp(paramt1, param2); obj = tmp; objPointer = new MyObject(param1, param2); [...verarbeitung...] } void MyClass::finalize() { delete objPointer; }C++ hat, im Gegensatz zu Java (C++ <3) Destruktoren, die bei Zerstörung automatisch aufgerufen werden. Mit der Suchmaschine deiner Wahl (Als ob jemand was anderes als Google verwenden würde) findest du dazu bestimmt was.
Abgesehen davon ist der Zeiger immer noch nicht notwendig.
HeXor schrieb:
Die main
//ohne params und bla, einfach nur main funktion halt main() { MyClass bar; bar.foo(); bar.finalize(); }main muss in C++ int zurückgeben. Allerdings ist es in der main erlaubt, kein return hinzuschreiben, in diesem Fall wird 0 zurückgegeben.
HeXor schrieb:
Der Pointer soll eben dann noch in vielen anderen Funktionen benutzt werden können.
Mit diesem Satz fange ich nichts an.
HeXor schrieb:
Und eigentlich müsste ich ja dann korrekter weise (auch wenn dann in diesem Beispiel das Ende der Programms erreicht ist) den Pointer am Ende mit finalize() aufräumen.
Nimm die oben von mir angesprochenen Destruktoren, dann musst du gar nichts machen.
P.S.: Der "fehlende" GC von C++ fehlt überhaupt nicht. Es ist so gewollt, dass C++ keinen hat. Mag sein, dass das für dich als Javaner erstmal total veraltet rüberkommt, allerdings hat C++ mit RAII einen sehr guten, meiner Meinung nach allen anderen Sprachen überlegenen Mechanismus fürs Resourcen-Management. In Java fällt sowas schnell mal unter den Tisch, von den ganzen hässlichen try-catch-finally Blöcken möchte ich gar nicht erst reden.
Es stimmt auch, dass GCs einige Probleme von manueller Speicherverwaltung (Fragmentierung, Zyklische Referenzen, ...) auf eine für den Programmierer einfache Weise lösen können. Letzteres kann jedoch bereits durch die Verwendung von weak_ptr umgangen werden, und gegen Fragmentierung hilft ein eigenen Allokator mit speziellen Smartpointern, die es erlauben, den Speicher auch mal rumzuschieben. Alles nicht sehr einfach, aber machbar.
P.P.S.: Ich vermute mal, dass dein Projekt mehrere tausend Zeilen Code hat, die du nun erstmal korrigieren musst. Ich fände es zwar besser, wenn du das tun würdest, allerdings gibt es auch für C++ GCs. Ein Beispiel ist der Hans Boehm GC.