Como posso ver, ponteiros inteligentes são amplamente utilizados em muitos projetos C ++ do mundo real.
Embora algum tipo de ponteiro inteligente seja obviamente benéfico para oferecer suporte a transferências de propriedade e RAII, também há uma tendência de usar ponteiros compartilhados por padrão , como uma forma de "coleta de lixo" , para que o programador não precise pensar muito na alocação. .
Por que os ponteiros compartilhados são mais populares do que integrar um coletor de lixo adequado como o Boehm GC ? (Ou você concorda que eles são mais populares que os GCs reais?)
Conheço duas vantagens dos GCs convencionais sobre a contagem de referência:
- Os algoritmos convencionais de GC não têm problemas com ciclos de referência .
- A contagem de referência é geralmente mais lenta que um GC adequado.
Quais são as razões para usar ponteiros inteligentes de contagem de referência?
c++
garbage-collection
Miklós Homolya
fonte
fonte
std::unique_ptr
é suficiente e, como tal, tem zero de sobrecarga em relação aos ponteiros brutos em termos de desempenho em tempo de execução. Ao usar emstd::shared_ptr
qualquer lugar, você também obscureceria a semântica da propriedade, perdendo um dos principais benefícios de indicadores inteligentes que não sejam o gerenciamento automático de recursos - a compreensão clara da intenção por trás do código.Respostas:
Algumas vantagens da contagem de referência sobre a coleta de lixo:
Baixa sobrecarga. Os coletores de lixo podem ser bastante intrusivos (por exemplo, congelar o programa em momentos imprevisíveis enquanto um ciclo de coleta de lixo é processado) e consumir muita memória (por exemplo, a pegada de memória do processo aumenta desnecessariamente para muitos megabytes antes que a coleta de lixo finalmente entre em ação)
Comportamento mais previsível. Com a contagem de referências, você garante que seu objeto será liberado no instante em que a última referência desaparecer. Com a coleta de lixo, por outro lado, seu objeto será liberado "algum dia", quando o sistema chegar a ele. Para a RAM, isso geralmente não é um grande problema em desktops ou servidores com pouca carga, mas para outros recursos (por exemplo, identificadores de arquivos), você geralmente precisa que eles sejam fechados o mais rápido possível para evitar possíveis conflitos mais tarde.
Mais simples. A contagem de referência pode ser explicada em alguns minutos e implementada em uma ou duas horas. Os coletores de lixo, especialmente aqueles com desempenho decente, são extremamente complexos e poucas pessoas os entendem.
Padrão. O C ++ inclui contagem de referência (via shared_ptr) e amigos no STL, o que significa que a maioria dos programadores de C ++ está familiarizado com ele e a maior parte do código C ++ funciona com ele. Porém, não há nenhum coletor de lixo C ++ padrão, o que significa que você deve escolher um e esperar que funcione bem para o seu caso de uso - e, se não funcionar, é problema seu corrigir, não o idioma.
Quanto às supostas desvantagens da contagem de referência - não detectar ciclos é um problema, mas que eu nunca encontrei pessoalmente nos últimos dez anos usando a contagem de referência. A maioria das estruturas de dados é naturalmente acíclica e, se você se deparar com uma situação em que precisa de referências cíclicas (por exemplo, ponteiro pai em um nó de árvore), poderá usar apenas um fraco_ptr ou um ponteiro C bruto para a "direção inversa". Desde que você esteja ciente do possível problema ao projetar suas estruturas de dados, isso não é problema.
Quanto ao desempenho, nunca tive um problema com o desempenho da contagem de referência. Eu tive problemas com o desempenho da coleta de lixo, em particular os congelamentos aleatórios em que o GC pode incorrer, para os quais a única solução ("não alocar objetos") também pode ser reformulada como "não use o GC" .
fonte
make_shared
retornar. Ainda assim, a latência tende a ser o maior problema em aplicativos em tempo real, mas o rendimento é mais importante em geral, e é por isso que o rastreamento de GCs é tão amplamente usado. Eu não seria tão rápido em falar mal deles.Para obter um bom desempenho de um GC, ele precisa ser capaz de mover objetos na memória. Em uma linguagem como C ++, na qual você pode interagir diretamente com os locais da memória, isso é praticamente impossível. (O Microsoft C ++ / CLR não conta porque introduz uma nova sintaxe para os ponteiros gerenciados pelo GC e, portanto, é efetivamente uma linguagem diferente.)
O Boehm GC, embora seja uma idéia bacana, é o pior dos dois mundos: você precisa de um malloc () mais lento que um bom GC e, portanto, perde o comportamento determinístico de alocação / desalocação sem o aumento de desempenho correspondente de um GC geracional . Além disso, é necessariamente conservador, por isso não necessariamente coletará todo o seu lixo.
Um GC bom e bem ajustado pode ser uma grande coisa. Mas em uma linguagem como C ++, os ganhos são mínimos e os custos geralmente não valem a pena.
Será interessante ver, no entanto, à medida que o C ++ 11 se torna mais popular, se as lambdas e a semântica de captura começam a levar a comunidade C ++ para os mesmos tipos de problemas de alocação e vida útil do objeto que fizeram com que a comunidade Lisp inventasse GCs no primeiro Lugar, colocar.
Veja também minha resposta para uma pergunta relacionada no StackOverflow .
fonte
Verdadeiro, mas, objetivamente, a grande maioria do código agora é escrita em linguagens modernas com rastreadores de coletores de lixo.
Essa é uma péssima idéia, porque você ainda precisa se preocupar com os ciclos.
Oh uau, há muitas coisas erradas na sua linha de pensamento:
O GC de Boehm não é um GC "adequado" em nenhum sentido da palavra. É realmente horrível. É conservador, portanto vaza e é ineficiente por design. Consulte: http://flyingfrogblog.blogspot.co.uk/search/label/boehm
Os ponteiros compartilhados são, objetivamente, nem de longe tão populares quanto o GC, porque a grande maioria dos desenvolvedores está usando os idiomas do GC agora e não precisa de ponteiros compartilhados. Basta olhar para Java e Javascript no mercado de trabalho em comparação com C ++.
Você parece estar restringindo a consideração ao C ++ porque, presumo, você acha que o GC é uma questão tangencial. Não é (a única maneira de obter um GC decente é projetar o idioma e a VM para ele desde o início), então você está introduzindo o viés de seleção. As pessoas que realmente querem uma coleta de lixo adequada não ficam com o C ++.
Você está restrito ao C ++, mas gostaria de ter um gerenciamento automático de memória.
fonte
No MacOS X e iOS, e com os desenvolvedores que usam Objective-C ou Swift, a contagem de referências é popular porque é tratada automaticamente, e o uso da coleta de lixo diminuiu consideravelmente, já que a Apple não suporta mais (soube que aplicativos usando a coleta de lixo será interrompida na próxima versão do MacOS X e a coleta de lixo nunca foi implementada no iOS). Na verdade, duvido seriamente que houvesse muito software usando a coleta de lixo quando estava disponível.
O motivo para se livrar da coleta de lixo: nunca funcionou de maneira confiável em um ambiente de estilo C, onde os ponteiros poderiam "escapar" para áreas não acessíveis pelo coletor de lixo. A Apple acredita e acredita que a contagem de referências é mais rápida. (Você pode fazer reivindicações aqui sobre velocidade relativa, mas ninguém foi capaz de convencer a Apple). E no final, ninguém usou a coleta de lixo.
A primeira coisa que qualquer desenvolvedor de MacOS X ou iOS aprende é como lidar com ciclos de referência, para que não seja um problema para um desenvolvedor real.
fonte
A maior desvantagem da coleta de lixo no C ++ é que é absolutamente impossível acertar:
No C ++, os ponteiros não vivem em sua própria comunidade murada, eles são misturados com outros dados. Como tal, não é possível distinguir um ponteiro de outros dados que, por acaso, possuem um padrão de bits que pode ser interpretado como um ponteiro válido.
Conseqüência: Qualquer coletor de lixo C ++ vazará objetos que devem ser coletados.
No C ++, você pode fazer aritmética de ponteiros para derivar ponteiros. Assim, se você não encontrar um ponteiro para o início de um bloco, isso não significa que esse bloco não possa ser referenciado.
Conseqüência: Qualquer coletor de lixo C ++ precisa levar em consideração esses ajustes, tratando qualquer sequência de bits que aponte para qualquer lugar dentro de um bloco, inclusive logo após o término, como um ponteiro válido que faça referência ao bloco.
Nota: Nenhum coletor de lixo C ++ pode manipular código com truques como estes:
É verdade que isso invoca um comportamento indefinido. Mas algum código existente é mais inteligente do que bom, e pode desencadear uma desalocação preliminar por um coletor de lixo.
fonte