IMO, les langages à ramassage d'ordures ont des problèmes complémentaires à ceux des langages sans ramassage d'ordures. Pour chaque problème, il existe un bogue non caractéristique de GC et un bogue caractéristique de GC - une responsabilité du programmeur non GC et une responsabilité du programmeur GC.
Les programmeurs GC peuvent croire qu'ils sont déchargés de la responsabilité de libérer les objets, mais les objets contiennent des ressources autres que la mémoire - des ressources qui doivent souvent être libérées en temps opportun afin qu'elles puissent être acquises ailleurs - par exemple, des poignées de fichiers, des verrous d'enregistrement, des mutex...
Là où un programmeur non GC aurait une référence pendante (et très souvent une référence qui n'est pas un bogue, puisqu'un drapeau ou un autre état la marquerait comme ne devant pas être utilisée), un programmeur GC a une fuite de mémoire. Ainsi, là où le programmeur non GC doit s'assurer que free/delete est appelé de manière appropriée, le programmeur GC doit s'assurer que les références indésirables sont annulées ou éliminées de manière appropriée.
Il est dit ici que les pointeurs intelligents ne traitent pas les cycles d'élimination des déchets. Ce n'est pas forcément vrai - il existe des schémas de comptage de références qui peuvent briser les cycles et qui garantissent également l'élimination opportune de la mémoire résiduelle, et au moins une implémentation Java a utilisé (et peut encore le faire) un schéma de comptage de références qui pourrait tout aussi bien être implémenté comme un schéma de pointeur intelligent en C++.
Collecte simultanée de cycles dans les systèmes à comptage de référence
Bien sûr, cela ne se fait pas normalement - en partie parce que vous pouvez tout aussi bien utiliser un langage GC, mais aussi en partie parce que cela briserait des conventions clés du C++. Vous voyez, une grande partie du code C++ - y compris la bibliothèque standard - s'appuie fortement sur la convention RAII (Resource Allocation Is Initialisation), et cela repose sur des appels de destructeurs fiables et opportuns. Dans tout GC qui gère les cycles, cela est tout simplement impossible. Lors de la rupture d'un cycle d'ordures, vous ne pouvez pas savoir quel destructeur appeler en premier sans aucun problème de dépendance - ce n'est peut-être même pas possible, car il peut y avoir plus de dépendances cycliques que de simples références mémoire. La solution - en Java etc, il n'y a aucune garantie que les finaliseurs seront appelés. La collecte d'ordures ne collecte qu'un type très spécifique d'ordures - la mémoire. Toutes les autres ressources doivent être nettoyées manuellement, comme elles l'auraient été en Pascal ou en C, et sans l'avantage de destructeurs fiables de type C++.
Résultat final : une grande partie du nettoyage qui est "automatisé" en C++ doit être effectué manuellement en Java, C#, etc. Bien sûr, "automatisé" a besoin de guillemets parce que le programmeur est responsable de s'assurer que delete est appelé correctement pour tous les objets alloués au tas - mais dans les langages GC, il y a des responsabilités différentes mais complémentaires du programmeur. D'une manière ou d'une autre, si le programmeur ne parvient pas à gérer correctement ces responsabilités, vous obtenez des bogues.
[ EDIT - il y a des cas où Java, C# etc. font évidemment un nettoyage fiable (mais pas nécessairement opportun), et les fichiers en sont un exemple. Il s'agit d'objets pour lesquels les cycles de référence ne peuvent pas se produire - soit parce que (1) ils ne contiennent pas de références du tout, (2) il existe une preuve statique que les références qu'ils contiennent ne peuvent pas directement ou indirectement mener à un autre objet du même type, ou (3) la logique d'exécution garantit que les cycles ne sont pas possibles, même si les chaînes/arbres/autres sont possibles. Les cas (1) et (2) sont extrêmement courants pour les objets de gestion de ressources par opposition aux nœuds de structure de données - peut-être universel. Le compilateur lui-même ne peut pas raisonnablement garantir (3), cependant. Ainsi, alors que les développeurs de la bibliothèque standard, qui écrivent les classes de ressources les plus importantes, peuvent garantir un nettoyage fiable pour celles-ci, la règle générale est toujours que le nettoyage fiable des ressources non-mémoire ne peut être garanti pour un GC, et cela pourrait affecter les ressources définies par l'application].
Franchement, passer d'un statut de non-CG à celui de CG (ou vice-versa) n'est pas une baguette magique. Cela peut faire disparaître les problèmes suspects habituels, mais cela signifie simplement que vous avez besoin de nouvelles compétences pour prévenir (et déboguer) un tout nouvel ensemble de suspects.
Un bon programmeur devrait dépasser le stade du "qui est de quel côté ?" et apprendre à gérer les deux.