Grundlegende Frage: Wann ruft ein Programm die Destruktormethode einer Klasse in C ++ auf? Mir wurde gesagt, dass es immer dann aufgerufen wird, wenn ein Objekt den Gültigkeitsbereich verlässt oder einem Objekt ausgesetzt istdelete
Spezifischere Fragen:
1) Wenn das Objekt über einen Zeiger erstellt wird und dieser Zeiger später gelöscht wird oder eine neue Adresse zum Zeigen erhält, ruft das Objekt, auf das es zeigte, seinen Destruktor auf (vorausgesetzt, nichts anderes zeigt darauf)?
2) Im Anschluss an Frage 1, was definiert, wann ein Objekt den Gültigkeitsbereich verlässt (nicht in Bezug darauf, wann ein Objekt einen bestimmten {Block} verlässt). Mit anderen Worten, wann wird ein Destruktor für ein Objekt in einer verknüpften Liste aufgerufen?
3) Möchten Sie jemals einen Destruktor manuell aufrufen?
quelle
Antworten:
Dies hängt von der Art der Zeiger ab. Beispielsweise löschen intelligente Zeiger ihre Objekte häufig, wenn sie gelöscht werden. Gewöhnliche Zeiger nicht. Das Gleiche gilt, wenn ein Zeiger auf ein anderes Objekt zeigt. Einige intelligente Zeiger zerstören das alte Objekt oder zerstören es, wenn es keine Referenzen mehr hat. Gewöhnliche Zeiger haben keine solchen Intelligenz. Sie enthalten lediglich eine Adresse und ermöglichen es Ihnen, Operationen an den Objekten auszuführen, auf die sie zeigen, indem Sie dies gezielt tun.
Das hängt von der Implementierung der verknüpften Liste ab. Typische Sammlungen zerstören alle enthaltenen Objekte, wenn sie zerstört werden.
Eine verknüpfte Liste von Zeigern würde also normalerweise die Zeiger zerstören, aber nicht die Objekte, auf die sie zeigen. (Was möglicherweise richtig ist. Dies können Verweise durch andere Zeiger sein.) Eine verknüpfte Liste, die speziell für Zeiger entwickelt wurde, kann jedoch die Objekte bei ihrer eigenen Zerstörung löschen.
Eine verknüpfte Liste von intelligenten Zeigern kann die Objekte automatisch löschen, wenn die Zeiger gelöscht werden, oder dies, wenn sie keine Referenzen mehr haben. Es liegt ganz bei Ihnen, die Teile auszuwählen, die das tun, was Sie wollen.
Sicher. Ein Beispiel wäre, wenn Sie ein Objekt durch ein anderes Objekt desselben Typs ersetzen möchten, aber keinen Speicher freigeben möchten, nur um es erneut zuzuweisen. Sie können das alte Objekt an Ort und Stelle zerstören und ein neues an Ort und Stelle erstellen. (Im Allgemeinen ist dies jedoch eine schlechte Idee.)
quelle
new Foo()
mit einem Großbuchstaben 'F' gemeint haben .)Foo myfoo("foo")
ist nicht Most Vexing Parse, aber eschar * foo = "foo"; Foo myfoo(foo);
ist.delete myFoo
vorher angerufen werdenFoo *myFoo = new Foo("foo");
? Oder würden Sie das neu erstellte Objekt löschen, nein?myFoo
vor derFoo *myFoo = new Foo("foo");
Linie. Diese Zeile erstellt eine brandneue Variable namensmyFoo
, die eine vorhandene Variable schattiert. In diesem Fall gibt es jedoch keine, da dasmyFoo
oben Gesagte im Geltungsbereich derif
endet.Andere haben die anderen Probleme bereits angesprochen, daher werde ich nur einen Punkt betrachten: Möchten Sie jemals ein Objekt manuell löschen?
Die Antwort ist ja. @ DavidSchwartz gab ein Beispiel, aber es ist ziemlich ungewöhnlich. Ich werde ein Beispiel geben, das unter der Haube dessen steht, was viele C ++ - Programmierer ständig verwenden:
std::vector
(undstd::deque
obwohl es nicht ganz so häufig verwendet wird).Wie die meisten Leute wissen,
std::vector
wird ein größerer Speicherblock zugewiesen, wenn Sie mehr Elemente hinzufügen, als die aktuelle Zuordnung aufnehmen kann. In diesem Fall verfügt es jedoch über einen Speicherblock, der mehr Objekte aufnehmen kann, als sich derzeit im Vektor befinden.Um dies zu verwalten,
vector
wird unter dem Deckmantel Rohspeicher über dasAllocator
Objekt zugewiesen (was, sofern Sie nichts anderes angeben, bedeutet, dass es verwendet wird::operator new
). Wenn Sie dann (zum Beispiel)push_back
ein Element zum hinzufügenvector
, verwendet der Vektor intern aplacement new
, um ein Element im (zuvor) nicht verwendeten Teil seines Speicherplatzes zu erstellen.Was passiert nun, wenn Sie
erase
einen Artikel aus dem Vektor haben? Es kann nicht einfach verwendet werdendelete
- das würde seinen gesamten Speicherblock freigeben; Es muss ein Objekt in diesem Speicher zerstören, ohne andere zu zerstören oder einen der von ihm gesteuerten Speicherblöcke freizugeben (wenn Sie beispielsweiseerase
5 Elemente aus einem Vektor und sofortpush_back
5 weitere Elemente auswählen, wird garantiert, dass der Vektor nicht neu zugewiesen wird Erinnerung, wenn Sie dies tun.Zu diesem Zweck zerstört der Vektor die Objekte im Speicher direkt, indem er den Destruktor explizit aufruft und nicht verwendet
delete
.Wenn vielleicht jemand anderes einen Container unter Verwendung eines zusammenhängenden Speichers ungefähr so
vector
schreibt wie ein Do (oder eine Variante davon, wie esstd::deque
wirklich der Fall ist), möchten Sie mit ziemlicher Sicherheit dieselbe Technik verwenden.Betrachten wir zum Beispiel, wie Sie Code für einen kreisförmigen Ringpuffer schreiben können.
Im Gegensatz zu den Standard - Containern dieser Anwendungen
operator new
undoperator delete
direkt. Für den realen Gebrauch möchten Sie wahrscheinlich eine Allokatorklasse verwenden, aber im Moment würde sie mehr ablenken als beitragen (IMO jedenfalls).quelle
new
, sind Sie für den Aufruf verantwortlichdelete
. Wenn Sie ein Objekt mit erstellenmake_shared
, ist das Ergebnisshared_ptr
dafür verantwortlich, die Anzahl zu halten und aufzurufen,delete
wenn die Verwendungsanzahl auf Null geht.new
(dh es handelt sich um ein Stapelobjekt).new
zuweisen .quelle
1) Objekte werden nicht 'über Zeiger' erstellt. Es gibt einen Zeiger, der jedem Objekt zugewiesen wird, das Sie "neu" sind. Angenommen, dies ist das, was Sie meinen. Wenn Sie für den Zeiger "Löschen" aufrufen, wird das Objekt, auf das sich der Zeiger bezieht, tatsächlich gelöscht (und der Destruktor aufgerufen). Wenn Sie den Zeiger einem anderen Objekt zuweisen, tritt ein Speicherverlust auf. Nichts in C ++ wird Ihren Müll für Sie sammeln.
2) Dies sind zwei getrennte Fragen. Eine Variable verlässt den Gültigkeitsbereich, wenn der Stapelrahmen, in dem sie deklariert ist, vom Stapel entfernt wird. Normalerweise verlassen Sie einen Block. Objekte in einem Heap verlassen nie den Gültigkeitsbereich, obwohl ihre Zeiger auf dem Stapel dies möglicherweise tun. Nichts garantiert, dass ein Destruktor eines Objekts in einer verknüpften Liste aufgerufen wird.
3) Nicht wirklich. Möglicherweise gibt es Deep Magic, das etwas anderes vorschlägt, aber normalerweise möchten Sie Ihre "neuen" Schlüsselwörter mit Ihren "Lösch" -Schlüsselwörtern abgleichen und alles in Ihren Destruktor einfügen, was erforderlich ist, um sicherzustellen, dass es sich ordnungsgemäß bereinigt. Wenn Sie dies nicht tun, kommentieren Sie den Destruktor mit spezifischen Anweisungen an alle Benutzer der Klasse, wie sie die Ressourcen dieses Objekts manuell bereinigen sollen.
quelle
Um eine detaillierte Antwort auf Frage 3 zu geben: Ja, es gibt (seltene) Fälle, in denen Sie den Destruktor explizit aufrufen könnten, insbesondere als Gegenstück zu einer neuen Platzierung, wie dasblinkenlight feststellt.
Um ein konkretes Beispiel dafür zu geben:
Der Zweck dieser Art von Dingen besteht darin, die Speicherzuordnung von der Objektkonstruktion zu entkoppeln.
quelle
Zeiger - Normale Zeiger unterstützen RAII nicht. Ohne eine explizite
delete
wird es Müll geben. Glücklicherweise hat C ++ automatische Zeiger , die dies für Sie erledigen!Bereich - Überlegen Sie, wann eine Variable für Ihr Programm unsichtbar wird . Normalerweise ist dies am Ende von
{block}
, wie Sie betonen.Manuelle Zerstörung - Versuchen Sie dies niemals. Lassen Sie einfach Scope und RAII die Magie für Sie tun.
quelle
std::auto_ptr
ist in C ++ 11 veraltet, ja. Wenn das OP tatsächlich über C ++ 11 verfügt, sollte esstd::unique_ptr
für einzelne Eigentümer oderstd::shared_ptr
für mehrere Eigentümer mit Referenzzählung verwendet werden.std::queue<std::shared_ptr>?
habe ich festgestellt , dasspipe()
zwischen einem Erzeuger und Verbraucher Thread viel einfacher Parallelität, wenn das Kopieren nicht zu teuer ist.Immer wenn Sie "neu" verwenden, dh eine Adresse an einen Zeiger anhängen oder sagen, Sie beanspruchen Speicherplatz auf dem Heap, müssen Sie ihn "löschen".
1. Ja, wenn Sie etwas löschen, wird der Destruktor aufgerufen.
2.Wenn der Destruktor der verknüpften Liste aufgerufen wird, wird der Destruktor der Objekte aufgerufen. Wenn es sich jedoch um Zeiger handelt, müssen Sie sie manuell löschen. 3.wenn der Platz von "neu" beansprucht wird.
quelle
Ja, ein Destruktor (auch bekannt als dtor) wird aufgerufen, wenn ein Objekt den Gültigkeitsbereich verlässt, wenn es sich auf dem Stapel befindet oder wenn Sie
delete
einen Zeiger auf ein Objekt aufrufen .Wenn der Zeiger über gelöscht wird,
delete
wird der dtor aufgerufen. Wenn Sie den Zeiger neu zuweisen, ohne ihndelete
vorher aufzurufen , tritt ein Speicherverlust auf, da das Objekt noch irgendwo im Speicher vorhanden ist. Im letzteren Fall wird der dtor nicht aufgerufen.Eine gute Implementierung einer verknüpften Liste ruft den dtor aller Objekte in der Liste auf, wenn die Liste zerstört wird (weil Sie entweder eine Methode aufgerufen haben, um sie zu zerstören, oder sie selbst den Gültigkeitsbereich verlassen hat). Dies ist implementierungsabhängig.
Ich bezweifle es, aber ich wäre nicht überrascht, wenn es da draußen einen merkwürdigen Umstand gibt.
quelle
Wenn das Objekt nicht über einen Zeiger erstellt wird (z. B. A a1 = A ();), wird der Destruktor aufgerufen, wenn das Objekt zerstört wird, immer dann, wenn die Funktion, in der das Objekt liegt, beendet ist. Beispiel:
Der Destruktor wird aufgerufen, wenn Code ausgeführt wird, um "finish" zu setzen.
Wenn das Objekt über einen Zeiger erstellt wird (z. B. A * a2 = new A ();), wird der Destruktor aufgerufen, wenn der Zeiger gelöscht wird (delete a2;). Wenn der Punkt vom Benutzer nicht explizit gelöscht oder mit a versehen wird Neue Adresse vor dem Löschen, der Speicherverlust ist aufgetreten. Das ist ein Fehler.
Wenn wir in einer verknüpften Liste std :: list <> verwenden, müssen wir uns nicht um den Desktruktor oder den Speicherverlust kümmern, da std :: list <> all dies für uns erledigt hat. In einer von uns selbst geschriebenen verknüpften Liste sollten wir den Desktruktor schreiben und den Zeiger explizit löschen. Andernfalls führt dies zu einem Speicherverlust.
Wir rufen selten einen Destruktor manuell auf. Es ist eine Funktion, die das System bereitstellt.
Tut mir leid für mein schlechtes Englisch!
quelle
Denken Sie daran, dass der Konstruktor eines Objekts unmittelbar nach der Zuweisung des Speichers für dieses Objekt aufgerufen wird und der Destruktor unmittelbar vor der Freigabe des Speichers dieses Objekts aufgerufen wird.
quelle