Pour ajouter au débat ici.
Il existe des problèmes connus avec la récupération de place, et leur compréhension permet de comprendre pourquoi il n'y en a pas en 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, ce n'est peut-être pas la performance en soi, mais la prévisibilité de la performance.
Il existe actuellement 2 familles de GC largement déployées:
- Type Mark-And-Sweep
- Type de comptage de références
Le Mark And Sweep
est plus rapide (moins d'impact sur les performances globales) mais il souffre d'un syndrome de "gel du monde": c'est-à-dire lorsque le GC entre en action, tout le reste est arrêté jusqu'à ce que le GC ait fait son nettoyage. 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 Reference Counting
est différent: le comptage de références ajoute des frais généraux, en particulier dans les environnements multithreading car vous devez avoir un comptage atomique. De plus, il y a le problème des cycles de référence, vous avez donc besoin d'un algorithme intelligent pour détecter ces cycles et les éliminer (généralement mis en œuvre par un "gel du monde" aussi, bien que moins fréquent). En général, à ce jour, ce type (même s'il est normalement plus réactif ou plutôt gelant moins souvent) est plus lent que le Mark And Sweep
.
J'ai vu un article d'implémenteurs Eiffel qui essayaient d'implémenter un Reference Counting
Garbage Collector qui aurait une performance globale 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 document 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 à part entière. Lecture recommandée si seulement je pouvais remettre mes mains sur le fichier PDF ...
2. L'acquisition de ressources est l'initialisation (RAII)
C'est un idiome courant en ce sens C++
que vous encapsulerez la propriété des ressources dans un objet pour vous assurer qu'elles sont correctement libérées. Il est principalement utilisé pour la mémoire car nous n'avons pas de récupération de place, mais il est néanmoins utile dans de nombreuses autres situations:
- serrures (multi-thread, file handle, ...)
- connexions (à une base de données, à un autre serveur, ...)
L'idée est de bien contrôler la durée de vie de l'objet:
- il doit être vivant tant que vous en avez besoin
- il devrait être tué quand vous en aurez fini
Le problème du GC est que s'il aide avec le premier et garantit finalement que plus tard ... cet "ultime" peut ne pas être suffisant. Si vous libérez un verrou, vous aimeriez vraiment qu'il soit libéré maintenant, afin qu'il ne bloque plus aucun appel!
Les langues avec GC ont deux contournements:
- n'utilisez pas GC lorsque l'allocation de pile est suffisante: c'est normalement pour des problèmes de performances, mais dans notre cas, cela aide vraiment car la portée définit la durée de vie
using
construire ... mais il est explicite (faible) RAII en C ++ RAII est implicite de sorte que l'utilisateur NE PEUT PAS faire l'erreur sans le savoir (en omettant le using
mot - clé)
3. Pointeurs intelligents
Les pointeurs intelligents apparaissent souvent comme une solution miracle pour gérer la mémoire C++
. Souvent, j'ai entendu: nous n'avons pas besoin de GC après tout, car nous avons des pointeurs intelligents.
On ne peut plus se tromper.
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 vous-même assez facilement.
Cependant, quand il faut partager la propriété, cela devient plus difficile: vous pouvez partager entre plusieurs threads et il y a quelques problèmes subtils avec la gestion du compte. Par conséquent, on va naturellement vers shared_ptr
.
C'est génial, c'est pour ça que Boost après tout, mais ce n'est pas une solution miracle. En fait, le principal problème shared_ptr
est qu'il émule un GC implémenté par Reference Counting
mais vous devez implémenter la détection de cycle par vous-même ... Urg
Bien sûr, il y a ce weak_ptr
truc, mais j'ai malheureusement déjà vu des fuites de mémoire malgré l'utilisation de à shared_ptr
cause de ces cycles ... et lorsque 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 définitivement faisable. En l'absence de GC, il faut être clair sur la propriété:
- préfèrent avoir un seul propriétaire à un moment donné, si possible
- sinon, assurez-vous que votre diagramme de classes n'a pas de cycle relatif à la propriété et cassez-les avec une application subtile de
weak_ptr
Donc en effet, ce serait génial d'avoir un GC ... mais ce n'est pas un problème trivial. Et en attendant, nous avons juste besoin de retrousser nos manches.