Die Java-Garbage Collection kümmert sich um tote Objekte auf dem Heap, friert aber manchmal die Welt ein. In C ++ muss ich aufrufen delete
, um ein erstelltes Objekt am Ende seines Lebenszyklus zu entsorgen.
Dies delete
scheint ein sehr niedriger Preis für eine nicht gefrierende Umgebung zu sein. Das Platzieren aller relevanten delete
Schlüsselwörter ist eine mechanische Aufgabe. Man kann ein Skript schreiben, das den Code durchläuft und Löschen platziert, sobald keine neuen Zweige ein bestimmtes Objekt verwenden.
Also, was sind die Vor- und Nachteile von Java in vs C ++ DIY-Modell der Garbage Collection.
Ich möchte keinen C ++ vs Java-Thread starten. Meine Frage ist anders.
Diese ganze GC-Sache - läuft es darauf hinaus, "nur ordentlich zu sein, nicht zu vergessen, von Ihnen erstellte Objekte zu löschen - und Sie benötigen keine dedizierte GC? Oder ist es eher so, als ob" das Entsorgen von Objekten in C ++ wirklich schwierig ist - ich verbringe 20% meiner Zeit damit und doch sind Speicherlecks ein häufiger Ort "?
quelle
new
geht in den Konstruktor Ihrer Klasse, der den Speicher verwaltet,delete
geht in den Destruktor. Von dort ist alles automatisch (Speicherung). Beachten Sie, dass dies im Gegensatz zur Speicherbereinigung für alle Arten von Ressourcen funktioniert, nicht nur für den Speicher. Mutexe werden in einem Konstruktor aufgenommen und in einem Destruktor freigegeben. Dateien werden in einem Konstruktor geöffnet und in einem Destruktor geschlossen.Antworten:
Der C ++ - Objektlebenszyklus
Wenn Sie lokale Objekte erstellen, müssen Sie diese nicht löschen: Der Compiler generiert Code, um sie automatisch zu löschen, wenn das Objekt den Gültigkeitsbereich verlässt
Wenn Sie Objektzeiger verwenden und Objekte im freien Speicher erstellen, müssen Sie darauf achten, das Objekt zu löschen, wenn es nicht mehr benötigt wird (wie Sie beschrieben haben). Leider kann dies in komplexer Software viel schwieriger sein, als es aussieht (z. B. was ist, wenn eine Ausnahme ausgelöst wird und der erwartete Löschteil nie erreicht wird?).
Glücklicherweise haben Sie in modernem C ++ (auch bekannt als C ++ 11 und höher) intelligente Zeiger, wie zum Beispiel
shared_ptr
. Sie zählen das erstellte Objekt auf sehr effiziente Weise, ähnlich wie es ein Garbage Collector in Java tun würde. Und sobald nicht mehr auf das Objekt verwiesen wird,shared_ptr
löscht der zuletzt aktive das Objekt für Sie. Automatisch. Wie Garbage Collector, jedoch jeweils ein Objekt und ohne Verzögerung (Ok: Sie benötigen besondere Sorgfalt und müssenweak_ptr
mit Zirkelverweisen umgehen).Fazit: Sie können heutzutage C ++ - Code schreiben, ohne sich um die Speicherzuordnung kümmern zu müssen. Dieser Code ist so leckagefrei wie bei einem GC, jedoch ohne den Freeze-Effekt.
Der Java-Objektlebenszyklus
Das Schöne ist, dass Sie sich keine Gedanken über den Objektlebenszyklus machen müssen. Sie erstellen sie einfach und Java kümmert sich um den Rest. Ein moderner GC identifiziert und die Objekte zerstören , die nicht mehr benötigt werden (einschließlich , wenn es zirkuläre Verweise zwischen toten Objekten).
Aufgrund dieses Komforts haben Sie leider keine wirkliche Kontrolle darüber, wann das Objekt wirklich gelöscht wird . Semantisch fällt die Löschung / Zerstörung mit der Speicherbereinigung zusammen .
Dies ist vollkommen in Ordnung, wenn Objekte nur in Bezug auf den Speicher betrachtet werden. Mit Ausnahme des Einfrierens, aber dies ist kein Todesfall (die Leute arbeiten daran). Ich bin kein Java-Experte, aber ich denke, dass die verzögerte Zerstörung es schwieriger macht, Lecks in Java zu identifizieren , da Referenzen versehentlich aufbewahrt werden, obwohl Objekte nicht mehr benötigt werden (dh Sie können das Löschen von Objekten nicht wirklich überwachen).
Was aber, wenn das Objekt andere Ressourcen als den Speicher steuern muss, z. B. eine geöffnete Datei, ein Semaphor oder einen Systemdienst? Ihre Klasse muss eine Methode zum Freigeben dieser Ressourcen bereitstellen. Und Sie müssen sicherstellen, dass diese Methode aufgerufen wird, wenn die Ressourcen nicht mehr benötigt werden. Stellen Sie in jedem möglichen Verzweigungspfad durch Ihren Code sicher, dass er auch bei Ausnahmen aufgerufen wird. Die Herausforderung ist dem expliziten Löschen in C ++ sehr ähnlich.
Schlussfolgerung: Der GC löst ein Speicherverwaltungsproblem. Die Verwaltung anderer Systemressourcen wird jedoch nicht behandelt. Das Fehlen einer "Just-in-Time" -Löschung kann das Ressourcenmanagement sehr schwierig machen.
Löschen, Speicherbereinigung und RAII
Wenn Sie das Löschen eines Objekts und den Destruktor steuern können, der beim Löschen aufgerufen werden soll, können Sie den RAII nutzen . Bei diesem Ansatz wird der Speicher nur als Sonderfall der Ressourcenzuweisung betrachtet und die Ressourcenverwaltung sicherer mit dem Objektlebenszyklus verknüpft, wodurch eine streng kontrollierte Ressourcennutzung sichergestellt wird.
quelle
new
außerhalb von Konstruktoren / intelligenten Zeigern und niemalsdelete
außerhalb eines Destruktors.Like garbage collector, but one object at a time and without delay (Ok: you need some extra care and weak_ptr to cope with circular references).
Die Referenzzählung kann jedoch kaskadieren. ZB Der letzte Verweis aufA
geht weg, hat aberA
auch den letzten Verweis aufB
, wer den letzten Verweis aufC
...And you'll have the responsibility to make sure that this method is called when the resources are no longer needed. In every possible branching path through your code, ensuring it is also invoked in case of exceptions.
Java und C # haben spezielle Blockanweisungen dafür.Wenn Sie so ein Skript schreiben können, herzlichen Glückwunsch. Du bist ein besserer Entwickler als ich. Bei weitem.
Die einzige Möglichkeit, Speicherlecks in praktischen Fällen tatsächlich zu vermeiden, sind sehr strenge Codierungsstandards mit sehr strengen Regeln, wer der Eigentümer eines Objekts ist und wann es freigegeben werden kann und muss, oder Tools wie intelligente Zeiger, die Verweise auf Objekte zählen und löschen Objekte, wenn die letzte Referenz weg ist.
quelle
Wenn Sie mit RAII korrekten C ++ - Code schreiben, schreiben Sie normalerweise keinen neuen oder löschen. Das einzige "Neue", das Sie schreiben, befindet sich in gemeinsam genutzten Zeigern, sodass Sie "Löschen" wirklich nie verwenden müssen.
quelle
new
, auch nicht mit gemeinsam genutzten Zeigern - Sie solltenstd::make_shared
stattdessen verwenden.make_unique
. Es ist wirklich sehr selten, dass Sie tatsächlich eine gemeinsame Eigentümerschaft benötigen.Das Leben von Programmierern zu erleichtern und Speicherlecks zu verhindern, ist ein wichtiger Vorteil der Speicherbereinigung, aber nicht nur einer. Ein weiterer Grund ist die Verhinderung der Speicherfragmentierung. Wenn Sie in C ++ ein Objekt mit dem
new
Schlüsselwort zuweisen , bleibt es an einer festen Position im Speicher. Dies bedeutet, dass Sie während der Ausführung der Anwendung Lücken im freien Speicher zwischen den zugewiesenen Objekten haben. Das Zuweisen von Speicher in C ++ muss daher notwendigerweise ein komplizierterer Prozess sein, da das Betriebssystem in der Lage sein muss, nicht zugewiesene Blöcke mit einer bestimmten Größe zu finden, die zwischen die Lücken passen.Die Speicherbereinigung kümmert sich darum, indem alle nicht gelöschten Objekte in den Speicher verschoben werden, sodass sie einen fortlaufenden Block bilden. Wenn Sie feststellen, dass die Speicherbereinigung einige Zeit in Anspruch nimmt, liegt dies wahrscheinlich an diesem Prozess und nicht an der Freigabe des Speichers. Der Vorteil davon ist, dass die Speicherzuweisung fast so einfach ist wie das Verschieben eines Zeigers auf das Ende des Stapels.
In C ++ ist das Löschen von Objekten also schnell, das Erstellen kann jedoch langsam sein. In Java dauert das Erstellen von Objekten überhaupt keine Zeit, aber Sie müssen ab und zu einige Reinigungsarbeiten durchführen.
quelle
Javas Hauptversprechen waren
Es scheint, als würde Java Ihnen garantieren, dass der Müll entsorgt wird (nicht unbedingt auf effiziente Weise). Wenn Sie C / C ++ verwenden, haben Sie sowohl Freiheit als auch Verantwortung. Sie können es besser machen als Javas GC, oder Sie können viel schlechter sein (
delete
alles zusammen überspringen und Probleme mit Speicherverlusten haben).Wenn Sie Code benötigen, der "bestimmte Qualitätsstandards erfüllt" und das "Preis- / Qualitätsverhältnis" optimiert, verwenden Sie Java. Wenn Sie bereit sind, zusätzliche Ressourcen (Zeit Ihrer Experten) zu investieren, um die Leistung unternehmenskritischer Anwendungen zu verbessern, verwenden Sie C.
quelle
Der große Unterschied, den die Garbage Collection macht, besteht nicht darin, dass Sie Objekte nicht explizit löschen müssen. Der viel größere Unterschied besteht darin, dass Sie keine Objekte kopieren müssen .
Dies hat Auswirkungen, die beim Entwerfen von Programmen und Schnittstellen im Allgemeinen allgegenwärtig werden. Lassen Sie mich nur ein kleines Beispiel geben, um zu zeigen, wie weitreichend dies ist.
Wenn Sie in Java etwas von einem Stapel entfernen, wird der Wert, der angezeigt wird, zurückgegeben, sodass Sie folgenden Code erhalten:
In Java ist dies ausnahmesicher, da wir nur einen Verweis auf ein Objekt kopieren, was garantiert ohne Ausnahme geschieht. Das gleiche gilt jedoch nicht für C ++. In C ++ bedeutet das Zurückgeben eines Werts , dass dieser Wert kopiert wird (oder zumindest bedeuten kann ), und bei einigen Typen, die eine Ausnahme auslösen könnten. Wenn die Ausnahme ausgelöst wird, nachdem der Artikel vom Stapel entfernt wurde, aber bevor die Kopie zum Empfänger gelangt, ist der Artikel durchgesickert. Um dies zu verhindern, verwendet der Stapel von C ++ einen etwas ungeschickteren Ansatz, bei dem das Abrufen des obersten Elements und das Entfernen des obersten Elements zwei separate Vorgänge sind:
Wenn die erste Anweisung eine Ausnahme auslöst, wird die zweite nicht ausgeführt. Wenn also beim Kopieren eine Ausnahme ausgelöst wird, bleibt das Element auf dem Stapel, als wäre überhaupt nichts passiert.
Das offensichtliche Problem ist, dass dies einfach ungeschickt und (für Leute, die es nicht benutzt haben) unerwartet ist.
Dasselbe gilt für viele andere Teile von C ++: Insbesondere für generischen Code durchdringt die Ausnahmesicherheit viele Teile des Designs - und dies ist zum großen Teil auf die Tatsache zurückzuführen, dass die meisten zumindest potenziell das Kopieren von Objekten (die möglicherweise werfen) beinhalten, wo Java würde nur neue Verweise auf vorhandene Objekte erstellen (die nicht geworfen werden können, sodass wir uns keine Gedanken über Ausnahmen machen müssen).
Soweit ein einfaches Skript einzufügen ,
delete
wo nötig: Wenn Sie statisch bestimmen können , wenn Elemente auf der Grundlage der Struktur des Quellcodes zu löschen, soll es wahrscheinlich nicht wurde mitnew
unddelete
an erster Stelle.Lassen Sie mich ein Beispiel für ein Programm geben, für das dies mit ziemlicher Sicherheit nicht möglich wäre: ein System zum Tätigen, Verfolgen, Abrechnen (usw.) von Telefonanrufen. Wenn Sie Ihr Telefon wählen, wird ein "Anruf" -Objekt erstellt. Das Anrufobjekt verfolgt, wen Sie angerufen haben, wie lange Sie mit ihnen gesprochen haben usw., um den Abrechnungsprotokollen entsprechende Datensätze hinzuzufügen. Das Aufrufobjekt überwacht den Hardwarestatus. Wenn Sie also auflegen, zerstört es sich selbst (unter Verwendung der allgemein diskutierten
delete this;
). Nur ist es nicht so trivial wie "wenn du auflegst". Sie können beispielsweise eine Telefonkonferenz einleiten, zwei Personen verbinden und auflegen. Der Anruf wird jedoch auch nach dem Auflegen zwischen diesen beiden Teilnehmern fortgesetzt (die Abrechnung kann sich jedoch ändern).quelle
Etwas, von dem ich glaube, dass es hier nicht erwähnt wurde, ist, dass es Effizienzvorteile gibt, die durch die Speicherbereinigung entstehen. In den am häufigsten verwendeten Java-Kollektoren ist der Hauptort, an dem Objekte zugewiesen werden, ein Bereich, der für einen Kopierkollektor reserviert ist. Wenn die Dinge beginnen, ist dieser Raum leer. Wenn Objekte erstellt werden, werden sie im großen offenen Bereich nebeneinander zugewiesen, bis im verbleibenden zusammenhängenden Bereich kein Objekt mehr zugewiesen werden kann. Der GC tritt ein und sucht nach Objekten in diesem Raum, die nicht tot sind. Es kopiert die lebenden Objekte in einen anderen Bereich und fügt sie zusammen (dh keine Fragmentierung). Der alte Raum wird als sauber betrachtet. Anschließend werden die Objekte weiterhin eng miteinander zugeordnet, und dieser Vorgang wird nach Bedarf wiederholt.
Dies hat zwei Vorteile. Das erste ist, dass keine Zeit für das Löschen der nicht verwendeten Objekte aufgewendet wird. Sobald die lebenden Objekte kopiert wurden, gilt der Schiefer als sauber und die toten Objekte werden einfach vergessen. In vielen Anwendungen leben die meisten Objekte nicht sehr lange, sodass die Kosten für das Kopieren des Live-Sets im Vergleich zu Einsparungen, die dadurch erzielt werden, dass Sie sich keine Sorgen um das Dead-Set machen müssen, günstig sind.
Der zweite Vorteil besteht darin, dass beim Zuweisen eines neuen Objekts nicht nach einem zusammenhängenden Bereich gesucht werden muss. Die VM weiß immer, wo das nächste Objekt platziert werden soll (Einschränkung: Vereinfachtes Ignorieren der Parallelität).
Diese Art der Sammlung und Zuordnung ist sehr schnell. Aus Sicht des Gesamtdurchsatzes ist es für viele Szenarien schwer zu übertreffen. Das Problem ist, dass einige Objekte länger leben, als Sie sie weiter kopieren möchten. Dies bedeutet letztendlich, dass der Kollektor von Zeit zu Zeit eine erhebliche Zeitspanne einlegen muss und wann dies passieren kann, kann unvorhersehbar sein. Abhängig von der Länge der Pause und der Art der Anwendung kann dies ein Problem sein oder auch nicht. Es gibt mindestens einen pausenlosen Sammler. Ich gehe davon aus, dass es einen Kompromiss zwischen geringerer Effizienz gibt, um die pausenlose Natur zu erreichen, aber einer der Gründer dieses Unternehmens (Gil Tene) ist ein Überexperte bei GC, und seine Präsentationen sind eine großartige Informationsquelle über GC.
quelle
Nach meiner persönlichen Erfahrung in C ++ und sogar C waren Speicherlecks nie ein großer Kampf, den man vermeiden konnte. Mit einem vernünftigen Testverfahren und Valgrind zum Beispiel wird jedes physische Leck, das durch einen Anruf
operator new/malloc
ohne entsprechende verursachtdelete/free
wird, oft schnell erkannt und behoben. Um fair zu sein, können einige große C ++ - oder C ++ - Codebasen der alten Schule sehr wahrscheinlich einige undurchsichtige Randfälle aufweisen, die hier und da einige Bytes Speicher physisch verlieren können, da dies nicht derdeleting/freeing
Fall ist, der unter dem Radar des Testens geflogen ist.Was die praktischen Beobachtungen angeht, sind die undichtesten Anwendungen, auf die ich stoße (wie bei Anwendungen, die immer mehr Speicher verbrauchen, je länger Sie sie ausführen, obwohl die Datenmenge, mit der wir arbeiten, nicht wächst), normalerweise nicht in C oder geschrieben C ++. Ich finde keine Dinge wie den Linux-Kernel oder die Unreal Engine oder sogar den nativen Code, der zur Implementierung von Java verwendet wird, in der Liste der undichten Software, auf die ich stoße.
Die bekannteste Art von undichter Software, auf die ich stoße, sind Dinge wie Flash-Applets, wie Flash-Spiele, obwohl sie die Speicherbereinigung verwenden. Und das ist kein fairer Vergleich, wenn man daraus etwas ableiten sollte, da viele Flash-Anwendungen von angehenden Entwicklern geschrieben wurden, denen wahrscheinlich solide technische Prinzipien und Testverfahren fehlen (und ich bin mir auch sicher, dass es qualifizierte Fachleute gibt, die mit GC arbeiten Kämpfe nicht mit undichter Software), aber ich würde jedem viel zu sagen haben, der glaubt, dass GC das Schreiben von undichter Software verhindert.
Baumelnde Zeiger
Aus meiner speziellen Domäne, Erfahrung und da ich hauptsächlich C und C ++ verwende (und ich gehe davon aus, dass die Vorteile von GC je nach unseren Erfahrungen und Bedürfnissen variieren werden), ist das Sofortigste, was GC für mich löst, nicht das Problem des praktischen Speicherverlusts baumelnder Zeigerzugriff, und das könnte in geschäftskritischen Szenarien buchstäblich ein Lebensretter sein.
Leider ersetzt GC in vielen Fällen, in denen ein ansonsten baumelnder Zeigerzugriff gelöst wird, dieselbe Art von Programmiererfehler durch einen logischen Speicherverlust.
Wenn Sie sich vorstellen, dass ein Flash-Spiel von einem angehenden Programmierer geschrieben wurde, speichert er möglicherweise Verweise auf Spielelemente in mehreren Datenstrukturen, sodass diese gemeinsam Eigentümer dieser Spielressourcen sind. Nehmen wir leider an, er macht einen Fehler, bei dem er vergessen hat, die Spielelemente aus einer der Datenstrukturen zu entfernen, als er zur nächsten Stufe überging, um zu verhindern, dass sie freigegeben werden, bis das gesamte Spiel beendet ist. Das Spiel scheint jedoch immer noch gut zu funktionieren, da die Elemente nicht gezeichnet werden oder die Benutzerinteraktion beeinträchtigen. Trotzdem verbraucht das Spiel immer mehr Speicher, während sich die Frameraten selbst zu einer Diashow entwickeln, während die versteckte Verarbeitung immer noch diese versteckte Sammlung von Elementen im Spiel durchläuft (deren Größe inzwischen explosiv geworden ist). Dies ist das Problem, auf das ich in solchen Flash-Spielen häufig stoße.
Nehmen wir nun an, derselbe angehende Entwickler hat das Spiel in C ++ geschrieben. In diesem Fall gibt es normalerweise nur eine zentrale Datenstruktur im Spiel, die den Speicher "besitzt", während andere auf diesen Speicher verweisen. Wenn er den gleichen Fehler macht, besteht die Möglichkeit, dass das Spiel beim Übergang zur nächsten Stufe aufgrund des Zugriffs auf baumelnde Zeiger abstürzt (oder schlimmer noch, etwas anderes als Absturz).
Dies ist die unmittelbarste Art von Kompromiss, den ich in meiner Domäne am häufigsten zwischen GC und keinem GC finde. Und ich interessiere mich eigentlich nicht sehr für GC in meiner Domäne, was nicht sehr geschäftskritisch ist, da die größten Probleme, die ich jemals mit undichter Software hatte, die zufällige Verwendung von GC in einem ehemaligen Team waren, die die oben beschriebenen Lecks verursachte .
In meiner speziellen Domäne bevorzuge ich es in vielen Fällen, dass die Software abstürzt oder ausfällt, da dies zumindest viel einfacher zu erkennen ist, als herauszufinden, warum die Software auf mysteriöse Weise explosive Speichermengen verbraucht, nachdem sie eine halbe Stunde lang ausgeführt wurde Geräte- und Integrationstests bestehen ohne Beanstandung (nicht einmal von Valgrind, da der Speicher beim Herunterfahren von GC freigegeben wird). Dies ist jedoch kein Slam für GC von meiner Seite oder ein Versuch zu sagen, dass es nutzlos ist oder so etwas, aber es war in den Teams, mit denen ich gegen undichte Software zusammengearbeitet habe, keine Silberkugel, auch nicht in der Nähe im Gegenteil, ich hatte die gegenteilige Erfahrung mit dieser einen Codebasis, bei der GC die leckeste ist, die mir jemals begegnet ist. Um fair zu sein, wussten viele Mitglieder dieses Teams nicht einmal, was schwache Referenzen waren.
Shared Ownership und Psychologie
Das Problem, das ich bei der Speicherbereinigung finde, die es so anfällig für "Speicherlecks" machen kann (und ich werde darauf bestehen, es als solches zu bezeichnen, wie das "Speicherleck" sich aus Sicht des Benutzers genauso verhält), liegt in den Händen von Diejenigen, die es nicht mit Sorgfalt verwenden, beziehen sich meiner Erfahrung nach zu einem gewissen Grad auf "menschliche Tendenzen". Das Problem mit diesem Team und der undichtesten Codebasis, auf die ich jemals gestoßen bin, war, dass sie den Eindruck hatten, dass GC es ihnen ermöglichen würde, nicht mehr darüber nachzudenken, wem Ressourcen gehören.
In unserem Fall hatten wir so viele Objekte, die sich gegenseitig referenzierten. Modelle verweisen zusammen mit der Materialbibliothek und dem Shader-System auf Materialien. Materialien verweisen auf Texturen zusammen mit der Texturbibliothek und bestimmten Shadern. Kameras speichern Verweise auf alle Arten von Szenenelementen, die vom Rendern ausgeschlossen werden sollten. Die Liste schien auf unbestimmte Zeit weiterzugehen. Dies führte dazu, dass fast jede umfangreiche Ressource im System an mehr als 10 anderen Stellen im Anwendungsstatus gleichzeitig besessen und auf Lebenszeit erweitert wurde, und das war sehr, sehr anfällig für menschliches Versagen, das sich in einem Leck niederschlug (und nicht) Ich spreche von Gigabyte in Minuten mit schwerwiegenden Usability-Problemen. Konzeptionell mussten alle diese Ressourcen nicht im Eigentum geteilt werden, sie hatten konzeptionell alle einen Eigentümer.
Wenn wir aufhören, darüber nachzudenken, wem welcher Speicher gehört, und glücklicherweise nur lebenslange Verweise auf Objekte überall speichern, ohne darüber nachzudenken, stürzt die Software nicht aufgrund von baumelnden Zeigern ab, sondern mit ziemlicher Sicherheit unter solchen Bedingungen Nachlässige Denkweise, fangen Sie an, Speicher wie verrückt auf eine Weise zu verlieren, die sehr schwer aufzuspüren ist und Tests entgeht.
Wenn der baumelnde Zeiger in meiner Domäne einen praktischen Vorteil hat, ist dies, dass er sehr schlimme Störungen und Abstürze verursacht. Und das gibt den Entwicklern zumindest den Anreiz, wenn sie etwas Zuverlässiges versenden möchten, über Ressourcenmanagement nachzudenken und die richtigen Maßnahmen zu ergreifen, um alle zusätzlichen Verweise / Zeiger auf ein Objekt zu entfernen, das konzeptionell nicht mehr benötigt wird.
Verwaltung von Anwendungsressourcen
Richtiges Ressourcenmanagement ist der Name des Spiels, wenn es darum geht, Lecks in langlebigen Anwendungen zu vermeiden, bei denen ein dauerhafter Zustand gespeichert wird, in dem Leckagen schwerwiegende Probleme mit der Framerate und der Benutzerfreundlichkeit verursachen würden. Und die korrekte Verwaltung von Ressourcen ist hier mit oder ohne GC nicht weniger schwierig. Die Arbeit ist nicht weniger manuell, um die entsprechenden Verweise auf nicht mehr benötigte Objekte zu entfernen, unabhängig davon, ob es sich um Zeiger oder lebenslange Referenzen handelt.
Das ist die Herausforderung in meinem Bereich, nicht zu vergessen,
delete
was wir tunnew
(es sei denn, wir sprechen von einer Amateurstunde mit schlechten Tests, Praktiken und Werkzeugen). Und es erfordert Nachdenken und Sorgfalt, ob wir GC verwenden oder nicht.Multithreading
Das andere Problem, das ich bei GC sehr nützlich finde, wenn es in meiner Domäne sehr vorsichtig verwendet werden kann, ist die Vereinfachung der Ressourcenverwaltung in Multithreading-Kontexten. Wenn wir darauf achten, lebenslange Verlängerungsreferenzen auf Ressourcen nicht an mehr als einer Stelle im Anwendungsstatus zu speichern, kann die lebenslange Verlängerung von GC-Referenzen äußerst nützlich sein, damit Threads eine Ressource, auf die zugegriffen wird, vorübergehend erweitern können, um sie zu erweitern Die Lebensdauer beträgt nur kurze Zeit, damit der Thread die Verarbeitung abschließen kann.
Ich denke, dass eine sehr sorgfältige Verwendung von GC auf diese Weise zu einer sehr korrekten Software führen kann, die nicht undicht ist und gleichzeitig das Multithreading vereinfacht.
Es gibt Möglichkeiten, dies zu umgehen, obwohl keine GC vorhanden ist. In meinem Fall vereinheitlichen wir die Darstellung der Szenenentität der Software mit Threads, die vorübergehend dazu führen, dass die Szenenressourcen vor einer Bereinigungsphase für kurze Zeit auf eine eher allgemeine Weise erweitert werden. Dies mag ein bisschen nach GC riechen, aber der Unterschied besteht darin, dass es sich nicht um ein "gemeinsames Eigentum" handelt, sondern nur um ein einheitliches Szenenverarbeitungsdesign in Threads, das die Zerstörung dieser Ressourcen verzögert. Dennoch wäre es viel einfacher, sich hier nur auf GC zu verlassen, wenn es für solche Multithreading-Fälle sehr sorgfältig mit gewissenhaften Entwicklern verwendet werden könnte, die darauf achten, schwache Referenzen in den relevanten persistenten Bereichen zu verwenden.
C ++
Schließlich:
In Modern C ++ sollten Sie dies im Allgemeinen nicht manuell tun. Es geht nicht einmal so sehr darum, zu vergessen, es zu tun. Wenn Sie die Ausnahmebehandlung in das Bild einbeziehen, kann, selbst wenn Sie einen entsprechenden
delete
Aufruf an unten geschrieben habennew
, etwas in die Mitte werfen und dendelete
Aufruf nie erreichen , wenn Sie sich nicht auf automatisierte Destruktoraufrufe verlassen, für die der Compiler dies einfügt Du.Mit C ++ müssen Sie praktisch eine solche manuelle Ressourcenbereinigung vermeiden (dies schließt das Vermeiden manueller Aufrufe zum Entsperren eines Mutex außerhalb eines dtors ein, es sei denn, Sie arbeiten in einem eingebetteten Kontext mit deaktivierten Ausnahmen und speziellen Bibliotheken, die absichtlich so programmiert sind, dass sie nicht ausgelöst werden zB und nicht nur Speicherfreigabe). Die Ausnahmebehandlung erfordert dies ziemlich genau, daher sollte die gesamte Ressourcenbereinigung größtenteils über Destruktoren automatisiert werden.
quelle