A verdadeira resposta é que a única maneira de criar um mecanismo de coleta de lixo seguro e eficiente é ter suporte em nível de idioma para referências opacas. (Ou, inversamente, falta de suporte no nível da linguagem para manipulação direta da memória.)
Java e C # podem fazer isso porque eles têm tipos de referência especiais que não podem ser manipulados. Isso dá ao runtime a liberdade de fazer coisas como mover objetos alocados na memória , o que é crucial para uma implementação de GC de alto desempenho.
Para o registro, nenhuma implementação moderna de GC usa contagem de referência , de modo que é completamente um arenque vermelho. Os GCs modernos usam a coleção geracional, onde novas alocações são tratadas essencialmente da mesma maneira que as alocações de pilha estão em uma linguagem como C ++, e então periodicamente quaisquer objetos recém-alocados que ainda estão vivos são movidos para um espaço "sobrevivente" separado e uma geração inteira de objetos é desalocada de uma só vez.
Essa abordagem tem vantagens e desvantagens: a vantagem é que as alocações de heap em uma linguagem que suporta GC são tão rápidas quanto as alocações de pilha em uma linguagem que não suporta GC, e a desvantagem é que objetos que precisam executar a limpeza antes de serem destruídos requerem um mecanismo separado (por exemplo, a palavra-chave using
do C #) ou então seu código de limpeza é executado de forma não determinística.
Observe que uma chave para um GC de alto desempenho é que deve haver suporte de idioma para uma classe especial de referências. C não tem esse suporte de idioma e nunca terá; porque o C ++ tem sobrecarga de operador, ele poderia emular um tipo de ponteiro GC, embora isso devesse ser feito com cuidado. Na verdade, quando a Microsoft inventou seu dialeto de C ++ que seria executado sob o CLR (o tempo de execução do .NET), eles precisavam inventar uma nova sintaxe para "referências em estilo C #" (por exemplo, Foo^
) para distingui-las de "C ++ - referências de estilo "(por exemplo, Foo&
).
O que o C ++ tem, e o que é regularmente usado pelos programadores de C ++, é ponteiros inteligentes , que são realmente apenas um mecanismo de contagem de referências. Eu não consideraria a contagem de referência como "verdadeira" GC, mas ela fornece muitos dos mesmos benefícios, ao custo de desempenho mais lento que o gerenciamento de memória manual ou GC verdadeiro, mas com a vantagem da destruição determinística.
No final do dia, a resposta realmente se resume a um recurso de design de linguagem. C fez uma escolha, C ++ fez uma escolha que permitiu que ela fosse compatível com C e ainda oferecesse alternativas boas o suficiente para a maioria das finalidades, e Java e C # fizeram uma escolha diferente que é incompatível com C mas também é boa o suficiente mais propósitos. Infelizmente, não existe uma bala de prata, mas estar familiarizado com as diferentes opções lá fora irá ajudá-lo a escolher o correto para qualquer programa que você está tentando construir.