Warum hat C ++ keinen Garbage Collector?

Ich stelle diese Frage nicht wegen der Vorteile der Garbage-Collection vor allem. Mein Hauptgrund dafür ist, dass ich weiß, dass Bjarne Stroustrup gesagt hat, dass C ++ irgendwann einen Müllsammler haben wird.

Mit dem gesagt, warum wurde es nicht hinzugefügt? Es gibt bereits einige Garbage Collectors für C ++. Ist das nur eines von diesen “leichter gesagt als getanen” Dingen? Oder gibt es andere Gründe, warum es nicht hinzugefügt wurde (und nicht in C ++ 11 hinzugefügt wird)?

Querverbindungen:

  • Müllsammler für C ++

Nur um zu verdeutlichen, verstehe ich die Gründe, warum C ++ beim ersten Erstellen keinen Garbage Collector hatte. Ich frage mich, warum der Sammler nicht hinzugefügt werden kann.

Implizite Garbage-Collection hätte hinzugefügt werden können, aber es hat den Schnitt nicht gemacht. Wahrscheinlich nicht nur wegen Komplikationen bei der Umsetzung, sondern auch weil die Menschen nicht schnell genug zu einem allgemeinen Konsens kommen konnten.

Ein Zitat von Bjarne Stroustrup selbst:

Ich hatte gehofft, dass ein Garbage Collector, der optional aktiviert werden könnte, Teil von C ++ 0x sein würde, aber es gab genug technische Probleme, die ich mit einer detaillierten Spezifikation, wie ein solcher Collector in den Rest der Sprache integriert werden kann , falls vorhanden. Wie es bei im Wesentlichen allen C ++ 0x-Merkmalen der Fall ist, existiert eine experimentelle Implementierung.

Es gibt eine gute Diskussion des Themas hier .

Gesamtübersicht:

C ++ ist sehr mächtig und erlaubt Ihnen fast alles zu machen. Aus diesem Grund werden nicht viele Dinge automatisch auf Sie übertragen, die die performance beeinträchtigen könnten. Die Speicherbereinigung kann einfach mit intelligenten pointersn implementiert werden (Objekten, die pointers mit einer Referenzzählung umhüllen, die sich automatisch löschen, wenn die Referenzzählung 0 erreicht).

C ++ wurde unter Berücksichtigung der Konkurrenz entwickelt, die keine Garbage Collection hatte. Effizienz war das Hauptanliegen, das C ++ im Vergleich zu C und anderen von Kritik abwehren musste.

Es gibt 2 Arten von Müllabfuhr …

Explizite Speicherbereinigung:

C ++ 0x wird eine Speicherbereinigung über pointers haben, die mit shared_ptr erstellt wurden

Wenn Sie es wollen, können Sie es verwenden, wenn Sie es nicht wollen, sind Sie nicht gezwungen, es zu benutzen.

Sie können derzeit boost: shared_ptr verwenden, wenn Sie nicht auf C ++ 0x warten möchten.

Implizite Speicherbereinigung:

Es hat jedoch keine transparente Speicherbereinigung. Es wird jedoch ein Schwerpunkt für zukünftige C ++ – Spezifikationen sein.

Warum hat Tr1 keine implizite Garbage Collection?

Es gibt eine Menge Dinge, die tr1 von C ++ 0x haben sollte, Bjarne Stroustrup sagte in früheren Interviews, dass tr1 nicht so viel hatte, wie er es sich gewünscht hätte.

Um hier zur Debatte zu kommen.

Es gibt bekannte Probleme mit der Garbage Collection, und wenn Sie sie verstehen, können Sie besser verstehen, warum es in C ++ keine gibt.

1. performance?

Die erste Beschwerde betrifft oft die performance, aber die meisten Leute wissen nicht wirklich, wovon sie reden. Wie von Martin Beckett das Problem möglicherweise nicht performance an sich, sondern die Vorhersehbarkeit der performance.

Derzeit gibt es zwei GC-Familien, die weit verbreitet sind:

  • Mark-and-Sweep-Art
  • Referenz-Zählen Art

Der Mark And Sweep ist schneller (weniger Einfluss auf die Gesamtleistung), leidet aber unter einem “Freeze the World” -Syndrom: Wenn der GC zum Einsatz kommt, wird alles andere gestoppt, bis der GC seine Säuberung vorgenommen hat. Wenn Sie einen Server erstellen möchten, der in wenigen Millisekunden antwortet, werden einige Transaktionen nicht Ihren Erwartungen entsprechen 🙂

Das Problem des Reference Counting ist anders: Referenzzählen führt zu Mehraufwand, insbesondere in Multi-Threading-Umgebungen, da Sie eine atomare Zählung benötigen. Darüber hinaus gibt es das Problem der Referenzzyklen, so dass Sie einen ausgeklügelten Algorithmus benötigen, um diese Zyklen zu erkennen und zu eliminieren (in der Regel auch durch “Einfrieren der Welt”, wenn auch weniger häufig). Im Allgemeinen ist diese Art heutzutage (obwohl sie normalerweise reaktionsfreudiger ist, oder eher selten friert) langsamer als der Mark And Sweep .

Ich habe ein Papier von Eiffel-Implementierern gesehen, die versuchten, einen Reference Counting Garbage Collector zu implementieren, der eine ähnliche globale performance wie Mark And Sweep ohne den Aspekt “Freeze The World” haben würde. Es erforderte einen separaten Thread für den GC (typisch). Der Algorithmus war ein bisschen beängstigend (am Ende), aber das Papier machte eine gute Arbeit, indem es die Konzepte nacheinander einführte und die Evolution des Algorithmus von der “einfachen” Version bis zur vollwertigen zeigte. Leseempfehlung wenn ich nur meine Hände wieder in die PDF-Datei legen könnte …

2. Die Ressourcenerfassung wird initialisiert

Es ist ein gängiges Idiom in C++ dass Sie die Eigentumsrechte an Ressourcen innerhalb eines Objekts umbrechen, um sicherzustellen, dass sie ordnungsgemäß freigegeben werden. Es wird hauptsächlich für Speicher verwendet, da wir keine Speicherbereinigung haben, aber es ist auch nützlich für viele andere Situationen:

  • Sperren (Multithread, Dateihandle, …)
  • Verbindungen (zu einer database, einem anderen Server, …)

Die Idee ist, die Lebensdauer des Objekts richtig zu steuern:

  • es sollte so lange am Leben sein, wie Sie es brauchen
  • Es sollte getötet werden, wenn Sie damit fertig sind

Das Problem von GC ist, dass wenn es hilft mit dem ersteren und letztendlich garantiert, dass später … dieses “ultimative” möglicherweise nicht ausreichend ist. Wenn Sie eine Sperre aufheben, möchten Sie sie jetzt freigeben, damit sie keine weiteren Anrufe blockiert!

Sprachen mit GC haben zwei Arbeitsumgebungen:

  • Verwenden Sie GC nicht, wenn die Stapelzuweisung ausreichend ist: Normalerweise werden performancesprobleme verursacht, in unserem Fall ist es jedoch sehr hilfreich, da der Bereich die Lebensdauer definiert
  • using construct … aber es ist explizit (schwach) RAII, während in C ++ RAII implizit ist, so dass der Benutzer den Fehler NICHT unwissentlich machen kann (indem er das using Schlüsselwort weglässt)

3. Intelligente pointers

Intelligente pointers erscheinen oft als eine silberne Kugel, um Speicher in C++ . Oft habe ich schon gehört: Wir brauchen den GC doch nicht, da wir pfiffige pointers haben.

Man könnte nicht falscher sein.

Intelligente pointers helfen: auto_ptr und unique_ptr verwenden RAII-Konzepte, sehr nützlich. Sie sind so einfach, dass Sie sie ganz einfach selbst schreiben können.

Wenn jemand das Eigentumsrecht teilen muss, wird es jedoch schwieriger: Sie können sich mehrere Threads teilen und es gibt ein paar subtile Probleme bei der Verarbeitung der Anzahl. Daher geht man natürlich auf shared_ptr .

Es ist großartig, das ist doch Boost, aber es ist keine Wunderwaffe. Das Hauptproblem bei shared_ptr besteht darin, dass es einen GC simuliert, der durch Reference Counting implementiert wird, aber Sie müssen die Zykluserkennung selbst implementieren … Urg

Natürlich gibt es dieses weak_ptr Ding, aber ich habe leider schon Speicherlecks gesehen, trotz der Verwendung von shared_ptr wegen dieser Zyklen … und wenn man sich in einer Multi-Threaded-Umgebung befindet, ist es extrem schwierig zu erkennen!

4. Was ist die Lösung?

Es gibt keine Wunderwaffe, aber wie immer ist es definitiv machbar. In Abwesenheit von GC muss man sich über das Eigentum klar sein:

  • bevorzugen, wenn möglich, einen einzelnen Besitzer zu einem gegebenen Zeitpunkt zu haben
  • Wenn nicht, vergewissern Sie sich, dass Ihr classndiagramm keinen Zyklus hat, der sich auf die Eigentumsrechte bezieht, und brechen Sie sie mit der subtilen Anwendung von weak_ptr

In der Tat wäre es großartig, einen GC zu haben … aber das ist kein triviales Problem. Und in der Zwischenzeit müssen wir nur die Ärmel hochkrempeln.

Welche Art? Soll es für Embedded-Waschmaschinen-Controller, Handys, Workstations oder Supercomputer optimiert werden?
Sollte die GUI-Reaktionsfähigkeit oder das Laden von Servern priorisiert werden?
sollte es viel Speicher oder viel CPU verwenden?

C / c ++ wird nur in zu vielen verschiedenen Situationen verwendet. Ich vermute, dass so etwas wie Boost Smart Pointer für die meisten Benutzer ausreichen wird

Bearbeiten – Automatische Garbage Collectors sind nicht so sehr ein Problem der performance (Sie können immer mehr Server kaufen), es ist eine Frage der vorhersagbaren performance.
Nicht zu wissen, wann der GC ins Spiel kommt, ist wie ein narkoleptischer Fluglinienpilot, die meiste Zeit sind sie großartig – aber wenn Sie wirklich Reaktionsfähigkeit brauchen!

Einer der größten Gründe dafür, dass C ++ keine Garbage-Collection eingebaut hat, ist, dass die Garbage Collection, um mit Destruktoren gut zu spielen, wirklich sehr schwer ist. Soweit ich weiß, weiß niemand wirklich, wie man es vollständig triggers. Es gibt eine Menge Probleme zu bewältigen:

  • Deterministische Lebensdauer von Objekten (Referenzzählung gibt Ihnen dies, aber GC nicht. Obwohl es nicht so groß sein kann).
  • Was passiert, wenn ein Destruktor austriggers, wenn das Objekt Müll gesammelt wird? Die meisten Sprachen ignorieren diese Ausnahme, da es eigentlich keinen catch-Block gibt, zu dem sie transportiert werden kann, aber dies ist wahrscheinlich keine akzeptable Lösung für C ++.
  • Wie kann ich es aktivieren / deaktivieren? Natürlich wäre es wahrscheinlich eine Kompilierzeit Entscheidung, aber Code, der für GC vs Code, der für NOT GC geschrieben geschrieben wird, wird sehr unterschiedlich und wahrscheinlich inkompatibel sein. Wie versöhnen Sie das?

Dies sind nur einige der Probleme, mit denen wir konfrontiert sind.

Obwohl dies eine alte Frage ist, gibt es immer noch ein Problem, bei dem sich niemand angesprochen fühlt: Die Sammlung von Garbage ist fast unmöglich zu spezifizieren.

Insbesondere ist der C ++ – Standard sehr darauf bedacht, die Sprache im Hinblick auf extern beobachtbares Verhalten zu spezifizieren und nicht darauf, wie die Implementierung dieses Verhalten erreicht. Im Falle der Speicherbereinigung gibt es jedoch praktisch kein extern beobachtbares Verhalten.

Die allgemeine Idee der Speicherbereinigung besteht darin, dass sie einen vernünftigen Versuch unternehmen sollte, sicherzustellen, dass eine Speicherzuweisung erfolgreich ist. Leider ist es im Grunde unmöglich zu garantieren, dass eine Speicherzuweisung erfolgreich ist, selbst wenn Sie einen Garbage Collector in Betrieb haben. Dies gilt in gewissem Maße, insbesondere aber im Fall von C ++, da es (wahrscheinlich) nicht möglich ist, einen Kopiersammler (oder etwas ähnliches) zu verwenden, der Objekte im Speicher während eines Sammelzyklus bewegt.

Wenn Sie Objekte nicht verschieben können, können Sie keinen einzigen zusammenhängenden Speicherplatz für die Zuweisungen erstellen. Dies bedeutet, dass Ihr Heap (oder freier Speicher oder wie immer Sie es nennen möchten) kann und wahrscheinlich auch , im Laufe der Zeit fragmentiert werden. Dies kann wiederum verhindern, dass eine Zuweisung erfolgreich ist, selbst wenn mehr Speicher frei ist als der angeforderte Betrag.

Während es möglich sein könnte, mit einer Garantie zu kommen, die (im Wesentlichen) sagt, dass, wenn Sie wiederholt genau das gleiche Muster der Zuteilung wiederholt, und es das erste Mal erfolgreich war, wird es auch bei nachfolgenden Iterationen erfolgreich sein, vorausgesetzt, dass der zugewiesene Speicher wurde zwischen Iterationen unzugänglich. Das ist eine so schwache Garantie, dass es im Grunde nutzlos ist, aber ich sehe keine vernünftige Hoffnung, es zu stärken.

Trotzdem ist es stärker als das, was für C ++ vorgeschlagen wurde. Der vorherige Vorschlag [Warnung: PDF] (der fallengelassen wurde) garantierte überhaupt nichts. In 28 Seiten des Vorschlags, was Sie in Bezug auf extern beobachtbares Verhalten bekamen, war eine einzige (nicht-normative) Notiz, die sagte:

[Hinweis: Bei Garbage-Collection-Programmen sollte eine gehostete Implementierung mit hoher Qualität versuchen, die Menge an unerreichbarem Speicher zu maximieren, die sie zurückgewinnt. Endnote]

Zumindest für mich wirft dies eine ernste Frage bezüglich der Kapitalrendite auf. Wir werden bestehenden Code brechen (niemand ist sicher, wie viel, aber definitiv ziemlich viel), stellen neue Anforderungen an Implementierungen und neue Beschränkungen für Code, und was wir dafür bekommen, ist möglicherweise gar nichts?

Im besten Fall erhalten wir Programme, die, basierend auf Tests mit Java , wahrscheinlich etwa sechsmal so viel Speicher benötigen, um mit der gleichen Geschwindigkeit laufen zu können, wie sie es jetzt tun. Schlimmer noch, Garbage Collection war von Anfang an ein Teil von Java – C ++ hat dem Garbage Collector genug Beschränkungen auferlegt, dass es mit Sicherheit ein noch schlechteres Kosten-Nutzen-Verhältnis haben wird (auch wenn wir über das hinaus gehen, was der Vorschlag garantiert und davon ausgeht) etwas Nutzen).

Ich würde die Situation mathematisch zusammenfassen: das ist eine komplexe Situation. Wie jeder Mathematiker weiß, besteht eine komplexe Zahl aus zwei Teilen: Real und Imaginär. Es scheint mir, dass es hier um reale Kosten geht, aber um Vorteile, die (zumindest größtenteils) imaginär sind.

Wenn Sie eine automatische Speicherbereinigung wünschen, gibt es gute kommerzielle und öffentliche Speicherbereinigungsfunktionen für C ++. Für Anwendungen, bei denen die Garbage-Collection geeignet ist, ist C ++ eine ausgezeichnete, mit Garbage Collection gesammelte Sprache mit einer performance, die im Vergleich zu anderen Garbage Collection-Sprachen vorteilhaft ist. Eine Erläuterung der automatischen Speicherbereinigung in C ++ finden Sie unter Die C ++ – Programmiersprache (3. Edition). Siehe auch Hans-J. Boehms Website für C- und C ++ – Speicherbereinigung. Außerdem unterstützt C ++ Programmiertechniken, die eine sichere und implizite Speicherverwaltung ohne Garbage Collector ermöglichen.

Quelle: http://www.stroustrup.com/bs_faq.html#garbage-collection

Warum habe ich es nicht eingebaut? Wenn ich mich richtig erinnere, wurde es erfunden, bevor GC das Ding war , und ich glaube nicht, dass die Sprache GC aus mehreren Gründen hätte haben können (IE Rückwärtskompatibilität mit C).

Hoffe das hilft.

Stroustrup hat dazu auf der Going Native Konferenz 2013 einige gute Kommentare abgegeben.

Springen Sie einfach in diesem Video zu ca. 25m50s. (Ich würde empfehlen, das ganze Video tatsächlich zu sehen, aber das überspringt das Zeug über Müllsammlung.)

Wenn Sie eine wirklich gute Sprache haben, die es einfach macht (und sicher und vorhersehbar und leicht zu lesen und leicht zu lehren), mit Objekten und Werten auf direkte Weise umzugehen, vermeiden Sie (explizite) Verwendung der Heap, dann wollen Sie nicht einmal Garbage Collection.

Mit modernem C ++ und dem, was wir in C ++ 11 haben, ist die Speicherbereinigung nicht mehr wünschenswert, außer in begrenzten Umständen. Selbst wenn ein guter Garbage-Collector in einen der wichtigsten C ++ – Compiler eingebaut ist, denke ich, dass er nicht sehr oft verwendet wird. Es wird leichter , nicht schwerer, den GC zu vermeiden.

Er zeigt dieses Beispiel:

 void f(int n, int x) { Gadget *p = new Gadget{n}; if(x<100) throw SomeException{}; if(x<200) return; delete p; } 

Dies ist in C ++ unsicher. Aber es ist auch in Java unsicher! Wenn die function in C ++ früh zurückkehrt, wird das delete nie aufgerufen. Aber wenn Sie eine vollständige Speicherbereinigung hatten, wie in Java, erhalten Sie nur einen Vorschlag, dass das Objekt "irgendwann in der Zukunft" zerstört wird ( Update: es ist noch schlimmer, dass dies. Java verspricht nicht , den Finalizer jemals aufzurufen - es wird vielleicht nie genannt). Dies ist nicht gut genug, wenn Gadget eine geöffnete Dateikennung oder eine Verbindung zu einer database oder Daten enthält, die Sie für das spätere Schreiben in eine database gepuffert haben. Wir möchten, dass das Gadget sobald es fertig ist zerstört wird, um diese Ressourcen so schnell wie möglich zu befreien. Sie möchten nicht, dass Ihr databaseserver mit tausenden nicht mehr benötigten databaseverbindungen kämpft - er weiß nicht, dass Ihr Programm fertig ist.

Also, was ist die Lösung? Es gibt ein paar Ansätze. Der offensichtliche Ansatz, den Sie für die überwiegende Mehrheit Ihrer Objekte verwenden werden, ist:

 void f(int n, int x) { Gadget p = {n}; // Just leave it on the stack (where it belongs!) if(x<100) throw SomeException{}; if(x<200) return; } 

Dadurch müssen weniger Zeichen eingegeben werden. Es hat nichts new in die Quere kommen. Sie müssen Gadget zweimal Gadget . Das Objekt wird am Ende der function zerstört. Wenn dies das ist, was Sie wollen, ist das sehr intuitiv. Gadget verhalten sich genauso wie int oder double . Vorhersehbar, leicht zu lesen, leicht zu vermitteln. Alles ist ein "Wert". Manchmal ein großer Wert, aber Werte sind leichter zu lehren, weil Sie diese "Aktion auf Distanz" nicht haben, die Sie mit pointersn (oder Referenzen) bekommen.

Die meisten Objekte, die Sie erstellen, werden nur in der function verwendet, in der sie erstellt wurden, und wurden möglicherweise als Eingaben an untergeordnete functionen übergeben. Der Programmierer sollte nicht über "Speicherverwaltung" nachdenken müssen, wenn er Objekte zurückgibt oder anderweitig Objekte in weit voneinander getrennten Teilen der Software teilt.

scope und Lebensdauer sind wichtig. In den meisten Fällen ist es einfacher, wenn die Lebensdauer der Oszilloskope gleich ist. Es ist einfacher zu verstehen und einfacher zu unterrichten. Wenn Sie eine andere Lebensdauer wünschen, sollte es offensichtlich sein, den Code zu lesen, den Sie tun, z. B. mit shared_ptr . (Oder Rückgabe von (großen) Objekten nach Wert, unter Verwendung von move-semantics oder unique_ptr .

Dies könnte als ein Effizienzproblem erscheinen. Was ist, wenn ich ein Gadget von foo() möchte? Die Umzugs-Semantik von C ++ 11 macht es einfacher, große Objekte zurückzugeben. Schreib einfach Gadget foo() { ... } und es wird einfach funktionieren und schnell arbeiten. Sie müssen sich nicht mit && selbst verwirren, sondern die Dinge einfach nach Wert zurückgeben und die Sprache wird oft in der Lage sein, die notwendigen Optimierungen vorzunehmen. (Schon vor C ++ 03 haben Compiler eine bemerkenswert gute Arbeit geleistet, um unnötiges Kopieren zu vermeiden.)

Wie Stroustrup an anderer Stelle im Video sagte (Paraphrasieren): "Nur ein Informatiker würde darauf bestehen, ein Objekt zu kopieren und dann das Original zu zerstören. (Publikum lacht). Warum verschiebt man das Objekt nicht einfach an den neuen Ort? (nicht Informatiker) erwarten. "

Wenn Sie sicherstellen können, dass nur eine Kopie eines Objekts benötigt wird, ist es viel einfacher, die Lebensdauer des Objekts zu verstehen. Sie können auswählen, welche Lebenszeit-Richtlinie Sie möchten, und die Garbage-Collection ist dort, wenn Sie möchten. Aber wenn Sie die Vorteile der anderen Ansätze verstehen, werden Sie feststellen, dass die Garbage Collection am Ende der Liste der Einstellungen steht.

Wenn das für Sie nicht funktioniert, können Sie unique_ptr oder shared_ptr . Gut geschrieben C ++ 11 ist kürzer, einfacher zu lesen und einfacher zu lehren als viele andere Sprachen, wenn es um Speicherverwaltung geht.

Die Idee hinter C ++ war, dass Sie keine performanceseinbußen für functionen zahlen würden, die Sie nicht verwenden. Das Hinzufügen von Garbage Collection hätte daher bedeutet, dass einige Programme direkt auf der Hardware ausgeführt werden, so wie es C tut, und einige innerhalb einer Art Laufzeit-Virtual Machine.

Nichts hindert Sie daran, irgendeine Art von Smartpointern zu verwenden, die an einen Speicherbereinigungsmechanismus eines Drittanbieters gebunden sind. Ich erinnere mich, dass Microsoft so etwas mit COM gemacht hat und es nicht gut gegangen ist.

Um die meisten “Warum” -Fragen zu C ++ zu beantworten, lesen Sie Design und Entwicklung von C ++

Eines der grundlegenden Prinzipien hinter der ursprünglichen C-Sprache ist, dass Speicher aus einer Sequenz von Bytes besteht, und Code braucht sich nur darum zu kümmern, was diese Bytes genau in dem Moment bedeuten, in dem sie verwendet werden. Moderne C ermöglicht Compilern, zusätzliche Einschränkungen aufzuerlegen, aber C enthält – und C ++ behält – die Fähigkeit, einen pointers in eine Sequenz von Bytes zu zerlegen, jede Sequenz von Bytes mit denselben Werten in einen pointers zu assemblieren und dann diesen pointers zu verwenden Greife auf das frühere Objekt zu.

Während diese Fähigkeit in einigen Arten von Anwendungen nützlich oder sogar unentbehrlich sein kann, wird eine Sprache, die diese Fähigkeit beinhaltet, in ihrer Fähigkeit, jede Art von nützlicher und zuverlässiger Speicherbereinigung zu unterstützen, sehr eingeschränkt sein. Wenn ein Compiler nicht alles weiß, was mit den Bits gemacht wurde, aus denen ein pointers besteht, kann er nicht wissen, ob Informationen, die ausreichen, um den pointers zu rekonstruieren, irgendwo im Universum existieren könnten. Da es möglich wäre, dass diese Information auf eine Weise gespeichert wird, auf die der Computer nicht zugreifen könnte, selbst wenn er davon wüsste (z. B. könnten die Bytes, aus denen der pointers besteht, lange genug auf dem Bildschirm angezeigt werden, damit jemand schreiben kann) sie auf einem Stück Papier), kann es für einen Computer buchstäblich unmöglich sein zu wissen, ob ein pointers möglicherweise in der Zukunft verwendet werden könnte.

Eine interessante Eigenart von vielen von Müll gesammelten Frameworks ist, dass eine Objektreferenz nicht durch die darin enthaltenen Bitmuster definiert ist, sondern durch die Beziehung zwischen den Bits, die in der Objektreferenz gehalten werden, und anderen Informationen, die an anderer Stelle gehalten werden. Wenn in C und C ++ das in einem pointers gespeicherte Bitmuster ein Objekt identifiziert, identifiziert dieses Bitmuster dieses Objekt, bis das Objekt explizit zerstört wird. In einem typischen GC-System kann ein Objekt durch ein Bitmuster 0x1234ABCD zu einem Zeitpunkt dargestellt werden, aber der nächste GC-Zyklus könnte alle Referenzen auf 0x1234ABCD mit Referenzen auf 0x4321BABE ersetzen, woraufhin das Objekt durch das letztere Muster repräsentiert würde. Selbst wenn man das einer Objektreferenz zugeordnete Bitmuster anzeigen und später von der Tastatur zurücklesen würde, würde man nicht erwarten, dass dasselbe Bitmuster zur Identifizierung desselben Objekts (oder eines beliebigen Objekts) verwendbar wäre.

Because these days, C++ doesn’t need it anymore.

The situation, for code written these days (C++17 and following the official language Coding Guidelines ) is as follows:

  • Most memory ownership-related code is in libraries (especially those providing containers).
  • Most use of code involving memory ownership follows the RAII pattern , so allocation is made on construction and deallocation on destruction, which happens when exiting the scope in which something was allocated.
  • You do not explicitly allocate or deallocate memory directly .
  • Raw pointers do not own memory (if you’ve followed the guidelines), so you can’t leak by passing them around.
  • If you’re wondering how you’re going to pass the starting addresses of sequences of values in memory – you’ll be doing that with a span ; no raw pointer needed.
  • If you really need an owning “pointer”, you use C++’ standard-library smart pointers – they can’t leak, and are quite efficient. Alternatively, you can pass ownership across scope boundaries with “owner pointers” . These are uncommon and must be used explicitly; and they allow for partial static checking against leaks.

“Oh yeah? But what about…

… if I just write code the way we’ve written C++ so far?”

Indeed, you could just disregard all of the guidelines and write leaky application code – and it will compile and run (and leak), same as always.

But it’s not a “just don’t do that” situation, where the developer is expected to be virtuous and exercise a lot of self control; it’s just not simpler to write non-conforming code, nor is it faster to write, nor is it better-performing. Gradually it will also become more difficult to write, as you would face an increasing “impedance mismatch” with what conforming code provides and expects.

… if I reintrepret_cast ? Or do pointer arithmetic? Or other such hacks?”

Indeed, if you put your mind to it, you can write code that messes things up despite playing nice with the guidelines. Aber:

  1. You would do this rarely (in terms of places in the code, not necessarily in terms of fraction of execution time)
  2. You would only do this intentionally, not accidentally
  3. Doing so will stand out in a codebase conforming to the guidelines
  4. It’s the kind of code in which you would bypass the GC in another language anyway

… library development?”

If you’re a C++ library developer then you do write unsafe code involving raw pointers, and you are required to code carefully and responsibly – but these are self-contained pieces of code written by experts.

so, bottom line: There’s really no motivation to collect garbage generally, as you all but make sure not to produce garbage. GC is on the way to becoming a non-problem with C++.

That is not to say GC isn’t an interesting problem for certain specific applications, when you want to employ custom allocation and de-allocations strategies. For those you would want custom allocation and de-allocation, not a language-level GC.

All the technical talking is overcomplicating the concept.

If you put GC into C++ for all the memory automatically then consider something like a web browser. The web browser must load a full web document AND run web scripts. You can store web script variables in the document tree. In a BIG document in a browser with lots of tabs open, it means that every time the GC must do a full collection it must also scan all the document elements.

On most computers this means that PAGE FAULTS will occur. So the main reason, to answer the question is that PAGE FAULTS will occur. You will know this as when your PC starts making lots of disk access. This is because the GC must touch lots of memory in order to prove invalid pointers. When you have a bona fide application using lots of memory, having to scan all objects every collection is havoc because of the PAGE FAULTS. A page fault is when virtual memory needs to get read back into RAM from disk.

So the correct solution is to divide an application into the parts that need GC and the parts that do not. In the case of the web browser example above, if the document tree was allocated with malloc, but the javascript ran with GC, then every time the GC kicks in it only scans a small portion of memory and all PAGED OUT elements of the memory for the document tree does not need to get paged back in.

To further understand this problem, look up on virtual memory and how it is implemented in computers. It is all about the fact that 2GB is available to the program when there is not really that much RAM. On modern computers with 2GB RAM for a 32BIt system it is not such a problem provided only one program is running.

As an additional example, consider a full collection that must trace all objects. First you must scan all objects reachable via roots. Second scan all the objects visible in step 1. Then scan waiting destructors. Then go to all the pages again and switch off all invisible objects. This means that many pages might get swapped out and back in multiple times.

So my answer to bring it short is that the number of PAGE FAULTS which occur as a result of touching all the memory causes full GC for all objects in a program to be unfeasible and so the programmer must view GC as an aid for things like scripts and database work, but do normal things with manual memory management.

And the other very important reason of course is global variables. In order for the collector to know that a global variable pointer is in the GC it would require specific keywords, and thus existing C++ code would not work.

SHORT ANSWER: We don’t know how to do garbage collection efficiently (with minor time and space overhead) and correctly all the time (in all possible cases).

LONG ANSWER: Just like C, C++ is a systems language; this means it is used when you are writing system code, eg, operating system. In other words, C++ is designed, just like C, with best possible performance as the main target. The language’ standard will not add any feature that might hinder the performance objective.

This pauses the question: Why garbage collection hinders performance? The main reason is that, when it comes to implementation, we [computer scientists] do not know how to do garbage collection with minimal overhead, for all cases. Hence it’s impossible to the C++ compiler and runtime system to perform garbage collection efficiently all the time. On the other hand, a C++ programmer, should know his design/implementation and he’s the best person to decide how to best do the garbage collection.

Last, if control (hardware, details, etc.) and performance (time, space, power, etc.) are not the main constraints, then C++ is not the write tool. Other language might serve better and offer more [hidden] runtime management, with the necessary overhead.

When you compare C++ with Java, you can see immediately that C++ was not designed with implicit Garbage Collection in mind, while Java was.

Having things like arbitrary pointers in C-Style and deterministic destructors does not only slow down the performance of GC-implementations, it would also destroy backward compatibility for a large amount of C++-legacy-code.

In addition to that, C++ is a language that is intended to run as standalone executable instead of having a complex run-time environment.

All in all: Yes it would be possible to add Garbage Collection to C++, but for the sake of continuity it is better not to do so. The cost of doing so would be greater than the benefit.

Mainly for two reasons:

  1. Because it doesn’t need one (IMHO)
  2. Because it’s pretty much incompatible with RAII, which is the cornerstone of C++

C++ already offers manual memory management, stack allocation, RAII, containers, automatic pointers, smart pointers… That should be enough. Garbage collectors are for lazy programmers who don’t want to spend 5 minutes thinking about who should own which objects or when should resources be freed. That’s not how we do things in C++.

Imposing garbage collection is really a low level to high level paradigm shift.

If you look at the way strings are handled in a language with garbage collection, you will find they ONLY allow high level string manipulation functions and do not allow binary access to the strings. Simply put, all string functions first check the pointers to see where the string is, even if you are only drawing out a byte. So if you are doing a loop that processes each byte in a string in a language with garbage collection, it must compute the base location plus offset for each iteration, because it cannot know when the string has moved. Then you have to think about heaps, stacks, threads, etc etc.