Para adicionar ao debate aqui.
Existem problemas conhecidos com a coleta de lixo, e entendê-los ajuda a entender por que não há nenhum em C ++.
1. desempenho?
A primeira reclamação geralmente é sobre desempenho, mas a maioria das pessoas não percebe do que está falando. Conforme ilustrado pelo Martin Beckett
problema, pode não ser o desempenho em si, mas a previsibilidade do desempenho.
Atualmente, existem duas famílias de GC amplamente implantadas:
- Tipo de marcação e varredura
- Tipo de contagem de referência
A Mark And Sweep
é mais rápido (menos impacto no desempenho geral), mas sofre de uma "congelar o mundo" síndrome: ou seja, quando os GC entra em ação, tudo o resto está parado até que o GC tem feito a sua limpeza. Se você deseja construir um servidor que responda em alguns milissegundos ... algumas transações não corresponderão às suas expectativas :)
O problema de Reference Counting
é diferente: a contagem de referência aumenta a sobrecarga, especialmente em ambientes com vários segmentos, porque você precisa ter uma contagem atômica. Além disso, há o problema dos ciclos de referência, portanto, você precisa de um algoritmo inteligente para detectar e eliminá-los (geralmente implementado por um "congelamento do mundo" também, embora seja menos frequente). Em geral, a partir de hoje, esse tipo (embora normalmente seja mais responsivo ou melhor, congela com menos frequência) é mais lento que o Mark And Sweep
.
Eu vi um artigo de implementadores da Eiffel que estavam tentando implementar um Reference Counting
Garbage Collector que teria um desempenho global semelhante Mark And Sweep
sem o aspecto "Congelar o mundo". Exigia um thread separado para o GC (típico). O algoritmo era um pouco assustador (no final), mas o artigo fez um bom trabalho ao introduzir os conceitos um de cada vez e mostrar a evolução do algoritmo da versão "simples" para a versão completa. Leitura recomendada se eu pudesse colocar minhas mãos de volta no arquivo PDF ...
2. Aquisição de recursos é inicialização (RAII)
É um idioma comum, C++
pois você agrupará a propriedade dos recursos em um objeto para garantir que eles sejam liberados corretamente. É usado principalmente para memória, pois não temos coleta de lixo, mas também é útil para muitas outras situações:
- bloqueios (multithread, identificador de arquivo, ...)
- conexões (a um banco de dados, outro servidor, ...)
A idéia é controlar adequadamente a vida útil do objeto:
- deve estar vivo enquanto você precisar
- deve ser morto quando você terminar com isso
O problema do GC é que, se ele ajuda com o primeiro e garante que mais tarde ... esse "final" pode não ser suficiente. Se você liberar um bloqueio, realmente gostaria que ele fosse liberado agora, para que não bloqueie mais chamadas!
Os idiomas com GC têm duas soluções alternativas:
- não use GC quando a alocação de pilha for suficiente: normalmente é para problemas de desempenho, mas, no nosso caso, realmente ajuda, pois o escopo define a vida útil
using
construir ... mas é RAII explícito (fraco) enquanto estiver em C ++ RAII está implícito, de modo que o usuário NÃO PODE involuntariamente cometer o erro (omitindo a using
palavra - chave)
3. Ponteiros inteligentes
Ponteiros inteligentes geralmente aparecem como uma bala de prata para lidar com a memória C++
. Muitas vezes ouvi falar: afinal, não precisamos de GC, pois temos indicadores inteligentes.
Não se poderia estar mais errado.
Ponteiros inteligentes ajudam: auto_ptr
e unique_ptr
usam conceitos RAII, extremamente úteis. Eles são tão simples que você pode escrevê-los sozinho com bastante facilidade.
Quando é necessário compartilhar a propriedade, porém, fica mais difícil: você pode compartilhar entre vários threads e existem alguns problemas sutis no manuseio da contagem. Portanto, alguém naturalmente vai em direção shared_ptr
.
É ótimo, é para isso que serve o Boost, afinal, mas não é uma bala de prata. Na verdade, o principal problema shared_ptr
é que ele emula um GC implementado por, Reference Counting
mas você precisa implementar a detecção de ciclo sozinho.
É claro que existe isso weak_ptr
, mas infelizmente eu já vi vazamentos de memória, apesar do uso shared_ptr
devido a esses ciclos ... e quando você está em um ambiente com vários threads, é extremamente difícil de detectar!
4. Qual é a solução?
Não existe uma bala de prata, mas como sempre, é definitivamente viável. Na ausência de GC, é preciso ser claro quanto à propriedade:
- prefira ter um único proprietário ao mesmo tempo, se possível
- caso contrário, verifique se o diagrama de classes não possui nenhum ciclo referente à propriedade e quebre-os com a aplicação sutil de
weak_ptr
Portanto, seria ótimo ter um GC ... no entanto, não é um problema trivial. E nesse meio tempo, só precisamos arregaçar as mangas.