Pourquoi C ++ ne dispose-t-il pas d’un ramasse-miettes?

Je ne pose pas cette question à cause des mérites de la collecte des ordures en premier lieu. Ma principale raison de demander ceci est que je sais que Bjarne Stroustrup a dit que C ++ aura un ramasse-miettes à un moment donné.

Cela dit, pourquoi n’a-t-il pas été ajouté? Il existe déjà des ramasse-miettes pour C ++. Est-ce juste une de ces choses “plus faciles à dire qu’à faire”? Ou existe-t-il d’autres raisons pour lesquelles il n’a pas été ajouté (et ne sera pas ajouté en C ++ 11)?

Liens croisés:

  • Ramasse-miettes pour C ++

Juste pour clarifier, je comprends les raisons pour lesquelles C ++ n’a pas eu de ramasse-miettes lors de sa création. Je me demande pourquoi le collecteur ne peut pas être ajouté.

Le nettoyage de la mémoire implicite aurait pu être ajouté, mais il n’a tout simplement pas été résolu. Probablement à cause non seulement des complications de la mise en œuvre, mais aussi du fait que les personnes ne sont pas en mesure de parvenir à un consensus assez rapidement.

Une citation de Bjarne Stroustrup lui-même:

J’avais espéré qu’un récupérateur de mémoire qui pourrait éventuellement être activé ferait partie de C ++ 0x, mais il y avait suffisamment de problèmes techniques que je devais faire avec juste une spécification détaillée de la façon dont un tel collecteur s’intègre avec le rest du langage , si fourni Comme c’est le cas pour pratiquement toutes les fonctionnalités C ++ 0x, une implémentation expérimentale existe.

Il y a une bonne discussion sur le sujet ici .

Aperçu général:

C ++ est très puissant et vous permet de faire presque n’importe quoi. Pour cette raison, de nombreuses choses ne sont pas forcément affectées à votre performance. La récupération de place peut être facilement implémentée avec des pointeurs intelligents (objects qui encapsulent des pointeurs avec un compte de référence, qui se suppriment automatiquement lorsque le nombre de références atteint 0).

C ++ a été conçu en pensant à des concurrents qui ne disposaient pas de la récupération de mémoire. L’efficacité était la principale préoccupation selon laquelle le C ++ devait repousser les critiques par rapport à C et à d’autres.

Il existe 2 types de ramasse-miettes …

Récupération de mémoire explicite:

C ++ 0x aura la récupération de place via des pointeurs créés avec shared_ptr

Si vous le voulez, vous pouvez l’utiliser, si vous ne le voulez pas, vous n’êtes pas obligé de l’utiliser.

Vous pouvez actuellement utiliser boost: shared_ptr si vous ne voulez pas attendre C ++ 0x.

Récupération de place implicite:

Cependant, il ne dispose pas de la récupération de la mémoire transparente. Ce sera un point focal pour les futures spécifications C ++.

Pourquoi Tr1 n’a pas de récupération de place implicite?

Il y a beaucoup de choses que tr1 de C ++ 0x aurait dû avoir, Bjarne Stroustrup dans des interviews précédentes a déclaré que tr1 n’avait pas autant qu’il l’aurait souhaité.

Pour append au débat ici.

Il existe des problèmes connus avec la récupération de la mémoire et leur compréhension aide à comprendre pourquoi il n’en existe pas dans C ++.

1. Performance?

La première plainte concerne souvent la performance, mais la plupart des gens ne réalisent pas vraiment de quoi ils parlent. Comme l’illustre Martin Beckett le problème n’est peut-être pas la performance en soi, mais la prévisibilité des performances.

Il existe actuellement 2 familles de GC largement déployées:

  • Type Mark-And-Sweep
  • Type de comptage de référence

Le Mark And Sweep est plus rapide (moins d’impact sur les performances globales) mais il souffre d’un syndrome de “gel du monde”: lorsque le GC démarre, tout le rest est arrêté jusqu’à ce que le CPG soit nettoyé. Si vous souhaitez construire un serveur qui répond en quelques millisecondes … certaines transactions ne seront pas à la hauteur de vos attentes 🙂

Le problème du Reference Counting de Reference Counting est différent: le comptage de références ajoute des frais supplémentaires, en particulier dans les environnements multi-thread, car vous devez avoir un nombre atomique. En outre, il y a le problème des cycles de référence, il vous faut donc un algorithme intelligent pour détecter ces cycles et les éliminer (en général, ils implémentent par un gel du monde, bien que moins fréquent). En général, à ce jour, ce type (bien que normalement plus réactif ou plutôt moins figé) est plus lent que le Mark And Sweep .

J’ai vu un article publié par les exécutants d’Eiffel qui essayaient d’implémenter un ramasse-miettes de Reference Counting qui aurait une performance similaire à Mark And Sweep sans l’aspect “Freeze The World”. Il fallait un thread séparé pour le GC (typique). L’algorithme était un peu effrayant (à la fin), mais le papier a fait un bon travail en introduisant les concepts un par un et en montrant l’évolution de l’algorithme de la version “simple” à la version complète. Lecture recommandée si seulement je pouvais remettre mes mains sur le fichier PDF …

2. L’acquisition de ressources est une initialisation

En langage C++ il est courant d’utiliser la propriété des ressources dans un object pour s’assurer qu’elles sont correctement publiées. Il est principalement utilisé pour la mémoire car nous n’avons pas de récupération de mémoire, mais il est également utile dans de nombreuses autres situations:

  • serrures (multi-thread, handle de fichier, …)
  • connexions (vers une firebase database, un autre serveur, …)

L’idée est de contrôler correctement la durée de vie de l’object:

  • il devrait être vivant aussi longtemps que vous en avez besoin
  • il devrait être tué quand vous en avez fini avec

Le problème du GC est que si cela aide le premier et garantit finalement que plus tard … ce “ultime” peut ne pas être suffisant. Si vous libérez un verrou, vous aimeriez vraiment qu’il soit disponible maintenant, afin qu’il ne bloque plus les appels!

Les langues avec GC ont deux solutions:

  • n’utilisez pas le GC lorsque l’allocation de la stack est suffisante: normalement, cela concerne les problèmes de performances, mais dans notre cas, cela aide vraiment car la scope définit la durée de vie
  • using construct … mais c’est explicite (faible) RAII en C ++ RAII est implicite pour que l’utilisateur NE PEUT PAS commettre involontairement l’erreur (en omettant le mot-clé using )

3. Les pointeurs intelligents

Les pointeurs intelligents apparaissent souvent comme une solution miracle pour gérer la mémoire en C++ . Souvent, j’ai entendu: nous n’avons pas besoin de GC, car nous avons des pointeurs intelligents.

On ne pouvait pas être plus faux.

Les pointeurs intelligents aident: auto_ptr et unique_ptr utilisent des concepts RAII, extrêmement utiles en effet. Ils sont si simples que vous pouvez les écrire facilement.

Quand il faut partager la propriété, cela devient plus difficile: vous pouvez partager plusieurs threads et il y a quelques problèmes subtils avec le traitement du décompte. Par conséquent, on va naturellement vers shared_ptr .

C’est génial, c’est ce que Boost est après tout, mais ce n’est pas une solution miracle. En fait, le principal problème avec shared_ptr est qu’il émule un GC implémenté par Reference Counting mais que vous devez implémenter vous-même la détection de cycle … Urg

Bien sûr, il y a ce weak_ptr , mais j’ai malheureusement déjà vu des memory leaks malgré l’utilisation de shared_ptr cause de ces cycles … et quand vous êtes dans un environnement multi thread, c’est extrêmement difficile à détecter!

4. Quelle est la solution?

Il n’y a pas de solution miracle, mais comme toujours, c’est vraiment faisable. En l’absence de GC, il faut être clair sur la propriété:

  • préférez avoir un seul propriétaire à un moment donné, si possible
  • Si ce n’est pas le cas, assurez-vous que votre diagramme de classes n’a pas de cycle relatif à la propriété et les casse avec une application subtile de weak_ptr

Donc en effet, ce serait bien d’avoir un GC… mais ce n’est pas une question anodine. Et en attendant, il suffit de retrousser nos manches.

Quel genre? Devrait-il être optimisé pour les contrôleurs de machines à laver, les téléphones portables, les postes de travail ou les superordinateurs intégrés?
Doit-il donner la priorité à la réactivité de l’interface graphique ou au chargement du serveur?
devrait-il utiliser beaucoup de mémoire ou beaucoup de CPU?

C / c ++ est utilisé dans trop de circonstances différentes. Je soupçonne que quelque chose comme boost pointeurs intelligents sera suffisant pour la plupart des utilisateurs

Edit – Les éboueurs automatiques ne sont pas tellement un problème de performance (vous pouvez toujours acheter plus de serveur), c’est une question de performances prévisibles.
Ne pas savoir quand le GC va se lancer, c’est comme employer un pilote de ligne narcoleptique, la plupart du temps, c’est génial – mais quand vous avez vraiment besoin de réactivité!

L’une des principales raisons pour lesquelles C ++ n’a pas intégré la récupération de place est que la récupération de la mémoire pour jouer avec les destructeurs est vraiment très difficile. Pour autant que je sache, personne ne sait vraiment comment le résoudre complètement. Il y a beaucoup de problèmes à traiter:

  • les durées de vie déterministes des objects (le comptage des références vous donne ceci, mais GC ne le fait pas. Bien que cela puisse ne pas être une grosse affaire).
  • Que se passe-t-il si un destructeur lance lorsque l’object est en train d’être collecté? La plupart des langages ignorent cette exception, car il n’y a vraiment pas de bloc catch pour le transporter, mais ce n’est probablement pas une solution acceptable pour C ++.
  • Comment l’activer / désactiver? Naturellement, il s’agirait probablement d’une décision de compilation, mais le code écrit pour GC vs code écrit pour NOT GC sera très différent et probablement incompatible. Comment conciliez-vous cela?

Ce ne sont là que quelques-uns des problèmes rencontrés.

Bien que ce soit une vieille question, il y a toujours un problème que je ne vois aucunement abordé: la collecte des ordures est presque impossible à spécifier.

En particulier, le standard C ++ prend soin de spécifier le langage en termes de comportement observable en externe, plutôt que la manière dont l’implémentation réalise ce comportement. Dans le cas du ramassage des ordures, cependant, il n’y a pratiquement aucun comportement observable de l’extérieur.

L’ idée générale du ramassage des ordures est qu’il devrait faire une tentative raisonnable pour s’assurer qu’une allocation de mémoire réussira. Malheureusement, il est pratiquement impossible de garantir que toute allocation de mémoire réussira, même si vous avez un ramasse-miettes en opération. Cela est vrai dans une certaine mesure dans tous les cas, mais particulièrement dans le cas de C ++, car il est (probablement) impossible d’utiliser un collecteur de copie (ou quelque chose de similaire) qui déplace des objects en mémoire lors d’un cycle de collecte.

Si vous ne pouvez pas déplacer d’objects, vous ne pouvez pas créer un seul espace mémoire contigu à partir duquel effectuer vos allocations – et cela signifie que votre segment de mémoire (ou magasin gratuit, ou tout ce que vous préférez l’appeler) peut le faire et le fera probablement. , se fragmenter avec le temps. Cela, à son tour, peut empêcher une allocation, même s’il n’y a plus de mémoire libre que la quantité demandée.

Bien qu’il soit possible de trouver une garantie qui dit (essentiellement) que si vous répétez exactement le même schéma d’allocation à plusieurs resockets, et qu’il réussit la première fois, il continuera à réussir les itérations suivantes, à condition que la mémoire allouée est devenu inaccessible entre les itérations. C’est une garantie si faible que c’est essentiellement inutile, mais je ne vois aucun espoir raisonnable de le renforcer.

Malgré tout, c’est plus fort que ce qui a été proposé pour C ++. La proposition précédente [avertissement: PDF] (qui a été abandonnée) ne garantissait rien du tout. En 28 pages de proposition, ce que vous avez compris comme comportement observable de l’extérieur était une note unique (non normative) disant:

[Remarque: pour les programmes récupérés, une implémentation hébergée de haute qualité doit tenter de maximiser la quantité de mémoire inaccessible récupérée. —End note]

Au moins pour moi, cela soulève une sérieuse question sur le retour sur investissement. Nous allons casser le code existant (personne ne sait exactement combien, mais certainement pas mal), mettre en place de nouvelles exigences sur les implémentations et les nouvelles ressortingctions sur le code, et ce que nous obtenons en retour n’est probablement rien du tout?

Au mieux, nous obtenons des programmes qui, basés sur des tests avec Java , nécessiteront probablement environ six fois plus de mémoire pour fonctionner à la même vitesse que maintenant. Pire encore, la récupération des ordures faisait partie de Java dès le début – C ++ impose suffisamment de ressortingctions sur le ramasse-miettes pour que son rapport coût / bénéfice soit encore pire (même si la proposition certains avantages).

Je résumerais la situation mathématiquement: c’est une situation complexe. Comme tout mathématicien le sait, un nombre complexe comporte deux parties: réelle et imaginaire. Il me semble que ce que nous avons ici, ce sont des coûts réels, mais des avantages (au moins pour la plupart) imaginaires.

Si vous souhaitez une récupération automatique de la mémoire, il existe de bons ramasse-miettes pour le domaine commercial et public pour C ++. Pour les applications où le nettoyage de la mémoire est approprié, C ++ est un excellent langage de récupération de la mémoire avec des performances qui se comparent favorablement aux autres langages collectés. Voir Le langage de programmation C ++ (3rd Edition) pour une discussion sur la récupération automatique de la mémoire en C ++. Voir aussi Hans-J. Le site de Boehm pour la récupération de place C et C ++. En outre, C ++ prend en charge les techniques de programmation permettant à la gestion de la mémoire d’être sécurisée et implicite sans un collecteur de mémoire.

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

Quant à savoir pourquoi il ne l’a pas intégré, si je me souviens bien, il a été inventé avant que le GC était la chose , et je ne pense pas que le langage pourrait avoir eu GC pour plusieurs raisons (compatibilité avec IE)

J’espère que cela t’aides.

Stroustrup a fait de bons commentaires à ce sujet lors de la conférence Going Native 2013.

Il suffit de passer à environ 25m50 dans cette vidéo . (Je recommande de regarder la vidéo en entier, mais cela passe directement à la collecte des ordures.)

Lorsque vous avez un très bon langage, il est facile (et sûr, prévisible et facile à lire, et facile à enseigner) de traiter les objects et les valeurs de manière directe, en évitant l’utilisation (explicite) de tas, alors vous ne voulez même pas la collecte des ordures.

Avec le C ++ moderne, et tout ce que nous avons en C ++ 11, le nettoyage de la mémoire n’est plus souhaitable, sauf dans des circonstances limitées. En fait, même si un bon ramasse-miettes est intégré à l’un des principaux compilateurs C ++, je pense qu’il ne sera pas utilisé très souvent. Il sera plus facile , pas plus difficile, d’éviter le GC.

Il montre cet exemple:

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

Ceci est dangereux en C ++. Mais c'est aussi dangereux en Java! En C ++, si la fonction retourne plus tôt, la delete ne sera jamais appelée. Mais si vous disposiez d'un nettoyage complet, comme en Java, vous obtenez simplement une suggestion de destruction de l'object "à un moment donné" ( mise à jour: c'est encore pire. Java ne promet jamais d'appeler le finaliseur) - ça ne s'appelle peut-être jamais). Ce n'est pas suffisant si Gadget contient un descripteur de fichier ouvert, ou une connexion à une firebase database, ou des données que vous avez mises en mémoire tampon pour les écrire ultérieurement dans une firebase database. Nous voulons que le gadget soit détruit dès qu'il est terminé, afin de libérer ces ressources le plus rapidement possible. Vous ne voulez pas que votre serveur de firebase database soit aux sockets avec des milliers de connexions de bases de données inutiles - il ne sait pas que votre programme est terminé.

Alors, quelle est la solution? Il y a quelques approches. L'approche évidente que vous utiliserez pour la grande majorité de vos objects est la suivante:

 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; } 

Cela prend moins de caractères à taper. Il n'y a pas de new choses à faire. Vous n'avez pas besoin de saisir Gadget deux fois. L'object est détruit à la fin de la fonction. Si c'est ce que vous voulez, c'est très intuitif. Gadget s se comportent comme int ou double . Prévisible, facile à lire, facile à enseigner. Tout est une "valeur". Parfois, une grande valeur, mais les valeurs sont plus faciles à enseigner parce que vous n'avez pas cette action «à distance» que vous obtenez avec des pointeurs (ou des références).

La plupart des objects que vous créez ne sont utilisés que dans la fonction qui les a créés et peut-être transmis en tant qu’entrées aux fonctions enfants. Le programmeur ne devrait pas avoir à penser à la «gestion de la mémoire» lors du renvoi d'objects ou au partage d'objects sur des parties du logiciel largement séparées.

La scope et la durée de vie sont importantes. La plupart du temps, c'est plus facile si la durée de vie est la même que celle de la scope. C'est plus facile à comprendre et plus facile à enseigner. Lorsque vous voulez une durée de vie différente, il est évident que vous devez lire le code que vous faites, en utilisant par exemple shared_ptr . (Ou retourner des objects (volumineux) par valeur, en utilisant move-sémantics ou unique_ptr .

Cela peut sembler un problème d'efficacité. Que faire si je veux retourner un gadget à partir de foo() ? La sémantique de déplacement de C ++ 11 facilite le retour des gros objects. Il suffit d'écrire Gadget foo() { ... } et cela fonctionnera et fonctionnera rapidement. Vous n'avez pas besoin de jouer avec && vous-même, il suffit de retourner les choses par valeur et le langage sera souvent capable de faire les optimisations nécessaires. (Même avant C ++ 03, les compilateurs faisaient un travail remarquable pour éviter les copies inutiles.)

Comme Stroustrup l'a dit ailleurs dans la vidéo (paraphrasant): "Seul un informaticien insisterait pour copier un object, puis pour détruire l'original. (Le public rit). Pourquoi ne pas déplacer l'object directement vers le nouvel emplacement? (pas les informaticiens) attendent. "

Lorsque vous pouvez garantir qu'une seule copie d'un object est nécessaire, il est beaucoup plus facile de comprendre la durée de vie de l'object. Vous pouvez choisir la politique de durée de vie que vous souhaitez, et le nettoyage de la mémoire est disponible si vous le souhaitez. Mais lorsque vous comprenez les avantages des autres approches, vous constaterez que la récupération de place est au bas de votre liste de préférences.

Si cela ne fonctionne pas pour vous, vous pouvez utiliser unique_ptr ou, à défaut, shared_ptr . C ++ 11 bien écrit est plus court, plus facile à lire et plus facile à enseigner que de nombreux autres langages en matière de gestion de la mémoire.

L’idée derrière C ++ était que vous ne paieriez aucun impact sur les performances pour les fonctionnalités que vous n’utilisez pas. Ainsi, l’ajout de la récupération de la mémoire aurait signifié que certains programmes s’exécuteraient directement sur le matériel comme C et d’autres dans une sorte de machine virtuelle d’exécution.

Rien ne vous empêche d’utiliser des pointeurs intelligents liés à un mécanisme de récupération de place tiers. Je pense que Microsoft a fait quelque chose comme ça avec COM et que ça ne s’est pas bien passé.

Pour répondre à la plupart des questions “pourquoi” sur C ++, lisez Conception et évolution de C ++

L’un des principes fondamentaux du langage C d’origine est que la mémoire est composée d’une séquence d’octets, et le code n’a besoin que de savoir ce que signifient ces octets au moment exact où ils sont utilisés. Le C moderne permet aux compilateurs d’imposer des ressortingctions supplémentaires, mais C inclut – et C ++ conserve – la possibilité de décomposer un pointeur en une séquence d’octets, d’assembler une séquence d’octets contenant les mêmes valeurs dans un pointeur, puis d’utiliser ce pointeur pour accéder à l’object précédent

Bien que cette capacité puisse être utile – voire indispensable – dans certains types d’applications, la capacité d’un langage incluant cette capacité sera très limitée pour prendre en charge tout type de nettoyage de la mémoire utile et fiable. Si un compilateur ne sait pas tout ce qui a été fait avec les bits qui composent un pointeur, il n’aura aucun moyen de savoir si des informations suffisantes pour reconstruire le pointeur pourraient exister quelque part dans l’univers. Puisqu’il serait possible de stocker ces informations de manière à ce que l’ordinateur ne puisse pas y accéder même s’il les connaissait (par exemple, les octets constituant le pointeur pourraient avoir été affichés sur l’écran suffisamment longtemps pour permettre à quelqu’un d’écrire). sur un morceau de papier), il peut être littéralement impossible pour un ordinateur de savoir si un pointeur pourrait éventuellement être utilisé dans le futur.

Un aspect intéressant de nombreux frameworks collectés consiste en une référence d’object non définie par les modèles de bits qui y sont contenus, mais par la relation entre les bits contenus dans la référence d’object et d’autres informations conservées ailleurs. En C et C ++, si le modèle de bit stocké dans un pointeur identifie un object, ce dernier identifie cet object jusqu’à ce que l’object soit explicitement détruit. Dans un système GC typique, un object peut être représenté par un modèle de bit 0x1234ABCD à un moment donné, mais le cycle GC suivant peut remplacer toutes les références à 0x1234ABCD par des références à 0x4321BABE, l’object étant alors représenté par ce dernier. Même si l’on devait afficher le modèle de bit associé à une référence d’object et le relire ensuite au clavier, on ne s’attendait pas à ce que le même modèle de bit soit utilisable pour identifier le même object (ou n’importe quel object).

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 comstack 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. Mais:

  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++ comstackr 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 ssortingngs are handled in a language with garbage collection, you will find they ONLY allow high level ssortingng manipulation functions and do not allow binary access to the ssortingngs. Simply put, all ssortingng functions first check the pointers to see where the ssortingng is, even if you are only drawing out a byte. So if you are doing a loop that processes each byte in a ssortingng in a language with garbage collection, it must compute the base location plus offset for each iteration, because it cannot know when the ssortingng has moved. Then you have to think about heaps, stacks, threads, etc etc.