Quais implementações de ponteiro inteligente do C ++ estão disponíveis?

121

Comparações, prós, contras e quando usar?

Este é um spin-off de um encadeamento de coleta de lixo, onde o que eu pensava ser uma resposta simples gerou muitos comentários sobre algumas implementações específicas de ponteiros inteligentes, então parecia valer a pena começar um novo post.

Em última análise, a questão é quais são as várias implementações de ponteiros inteligentes em C ++ por aí e como elas se comparam? Apenas simples prós e contras ou exceções e dicas para algo que você acha que deveria funcionar.

Publiquei algumas implementações que usei ou pelo menos encaminhei e considerei usar como resposta abaixo e meu entendimento de suas diferenças e semelhanças que podem não ser 100% precisas, portanto, fique à vontade para verificar ou me corrigir de fato, conforme necessário.

O objetivo é aprender sobre alguns novos objetos e bibliotecas ou corrigir meu uso e entendimento das implementações existentes já amplamente em uso e terminar com uma referência decente para outros.

AJG85
fonte
5
Eu acho que isso deve ser postado novamente como resposta a esta pergunta, e a pergunta transformada em uma pergunta real. Caso contrário, acho que as pessoas vão fechar isso como "não uma pergunta real".
strager
3
Existem todos os tipos de outros indicadores inteligentes, por exemplo, indicadores inteligentes da ATL ou OpenSceneGraph'sosg::ref_ptr .
James McNellis
11
Há uma pergunta aqui?
Cody Gray
6
Eu acho que você entendeu mal std::auto_ptr. std::auto_ptr_refé um detalhe de design de std::auto_ptr. std::auto_ptrnão tem nada a ver com a coleta de lixo, seu objetivo principal é especificamente permitir a transferência segura de propriedade de exceção, especialmente em situações de chamada e retorno de função. std::unique_ptrsó pode resolver os "problemas" que você cita com contêineres padrão porque o C ++ foi alterado para permitir uma distinção entre mover e copiar e os contêineres padrão foram alterados para aproveitar isso.
CB Bailey
3
Você diz que não é especialista em indicadores inteligentes, mas seu resumo é bastante exaustivo e correto (exceto pela pequena queixa de auto_ptr_refser um detalhe de implementação). Ainda assim, concordo que você deve postar isso como resposta e reformular a pergunta para ser uma pergunta real. Isso pode servir como uma referência futura.
Konrad Rudolph

Respostas:

231

C ++ 03

std::auto_ptr- Talvez um dos originais sofria de síndrome do primeiro calado, fornecendo apenas instalações limitadas de coleta de lixo. A primeira desvantagem é que ela exige deletedestruição, tornando-os inaceitáveis ​​para reter objetos alocados em matriz ( new[]). Ele assume a propriedade do ponteiro, portanto, dois ponteiros automáticos não devem conter o mesmo objeto. A atribuição transferirá a propriedade e redefinirá o ponteiro automático rvalue para um ponteiro nulo. O que leva à talvez a pior desvantagem; eles não podem ser usados ​​em contêineres STL devido à impossibilidade de ser copiada. O golpe final para qualquer caso de uso é que eles devem ser descontinuados no próximo padrão do C ++.

std::auto_ptr_ref- Este não é um ponteiro inteligente; na verdade, é um detalhe de design usado em conjunto std::auto_ptrpara permitir a cópia e a atribuição em determinadas situações. Especificamente, ele pode ser usado para converter um não-const std::auto_ptrem um lvalue usando o truque de Colvin-Gibbons, também conhecido como construtor de movimento, para transferir a propriedade.

Pelo contrário, talvez std::auto_ptrnão tenha sido realmente planejado para ser usado como um ponteiro inteligente de uso geral para a coleta automática de lixo. A maioria dos meus conhecimentos e suposições limitados são baseados no Uso Efetivo do auto_ptr, de Herb Sutter, e eu o uso regularmente, embora nem sempre da maneira mais otimizada.


C ++ 11

std::unique_ptr- Este é o nosso amigo que o substituirá std::auto_ptr, será bastante semelhante, exceto com as principais melhorias para corrigir os pontos fracos de std::auto_ptrtrabalhar com matrizes, proteção de lvalue por meio de construtor de cópia privada, ser utilizável com contêineres e algoritmos STL, etc. e a pegada de memória é limitada, este é um candidato ideal para substituir, ou talvez mais apropriadamente descrito como proprietário, de indicadores brutos. Como o "único" implica, existe apenas um proprietário do ponteiro, assim como o anteriorstd::auto_ptr .

std::shared_ptr- Eu acredito que isso se baseia no TR1 e boost::shared_ptrmelhorou para incluir alias e aritmética de ponteiros. Em resumo, envolve um ponteiro inteligente contado em referência em torno de um objeto alocado dinamicamente. Como o "compartilhado" implica que o ponteiro pode pertencer a mais de um ponteiro compartilhado quando a última referência do último ponteiro compartilhado fica fora do escopo, o objeto será excluído adequadamente. Eles também são seguros para threads e podem lidar com tipos incompletos na maioria dos casos. std::make_sharedpode ser usado para construir eficientemente umstd::shared_ptr com uma alocação de heap usando o alocador padrão.

std::weak_ptr- Igualmente baseado em TR1 e boost::weak_ptr. Esta é uma referência a um objeto de propriedade de a std::shared_ptre, portanto, não impedirá a exclusão do objeto se a std::shared_ptrcontagem de referência cair para zero. Para obter acesso ao ponteiro bruto, você primeiro precisará acessar o std::shared_ptrchamando, lockque retornará um vazio std::shared_ptrse o ponteiro de propriedade expirou e já foi destruído. Isso é útil principalmente para evitar contagens de referência pendentes indefinidas ao usar vários ponteiros inteligentes.


Impulso

boost::shared_ptr- Provavelmente o mais fácil de usar nos cenários mais variados (STL, PIMPL, RAII, etc.), este é um ponteiro inteligente contado e referenciado compartilhado. Ouvi algumas queixas sobre desempenho e sobrecarga em algumas situações, mas devo tê-las ignorado porque não consigo me lembrar qual era o argumento. Aparentemente, era popular o suficiente para se tornar um objeto C ++ padrão pendente e não há inconvenientes sobre a norma em relação aos ponteiros inteligentes.

boost::weak_ptr- Assim como a descrição anterior de std::weak_ptr, com base nesta implementação, isso permite uma referência não proprietária a boost::shared_ptr. Você não chama surpreendentemente lock()para acessar o ponteiro compartilhado "forte" e deve verificar se ele é válido, pois já pode ter sido destruído. Apenas certifique-se de não armazenar o ponteiro compartilhado retornado e deixá-lo sair do escopo assim que terminar, caso contrário, você estará de volta ao problema de referência cíclica, onde suas contagens de referência serão interrompidas e os objetos não serão destruídos.

boost::scoped_ptr- Esta é uma classe simples de ponteiro inteligente com pouca sobrecarga, provavelmente projetada para uma alternativa de melhor desempenho e boost::shared_ptrquando utilizável. É comparável, std::auto_ptrprincipalmente porque não pode ser usado com segurança como elemento de um contêiner STL ou com vários ponteiros para o mesmo objeto.

boost::intrusive_ptr- Eu nunca usei isso, mas pelo meu entendimento ele foi projetado para ser usado ao criar suas próprias classes compatíveis com ponteiro inteligente. Você precisa implementar a referência contando você mesmo, também precisará implementar alguns métodos se quiser que sua classe seja genérica, além disso, você precisará implementar sua própria segurança de encadeamento. No lado positivo, isso provavelmente oferece a maneira mais personalizada de escolher exatamente quanto ou quão "esperteza" você deseja. intrusive_ptrnormalmente é mais eficiente do que, shared_ptrpois permite que você tenha uma única alocação de heap por objeto. (obrigado Arvid)

boost::shared_array- Isto é boost::shared_ptrpara matrizes. Basicamente new [], operator[]e, delete []é claro, são assados. Isso pode ser usado em contêineres STL e, tanto quanto sei, tudo boost:shared_ptrfaz, embora você não possa usá boost::weak_ptr-los. No entanto, você pode usar alternativamente um boost::shared_ptr<std::vector<>>para funcionalidade semelhante e recuperar a capacidade de usar boost::weak_ptrpara referências.

boost::scoped_array- Isto é boost::scoped_ptrpara matrizes. Assim como acontece com boost::shared_arraytoda a variedade necessária, a matriz é incorporada. Esta é não copiável e, portanto, não pode ser usada em contêineres STL. Eu encontrei quase qualquer lugar que você queira usar isso, provavelmente você pode usar std::vector. Eu nunca determinei o que é realmente mais rápido ou com menos sobrecarga, mas essa matriz com escopo parece muito menos envolvida que um vetor STL. Quando você deseja manter a alocação na pilha, considere boost::array.


Qt

QPointer- Introduzido no Qt 4.0, este é um ponteiro inteligente "fraco", que só funciona com QObjectclasses derivadas e que, na estrutura do Qt, é quase tudo, então isso não é realmente uma limitação. No entanto, existem limitações, a saber, que ele não fornece um ponteiro "forte" e, embora você possa verificar se o objeto subjacente é válido, isNull()poderá encontrar seu objeto sendo destruído logo após passar na verificação, especialmente em ambientes com vários segmentos. As pessoas Qt consideram isso obsoleto, acredito.

QSharedDataPointer- Este é um ponteiro inteligente "forte", potencialmente comparável, boost::intrusive_ptrembora tenha alguns recursos de segurança de encadeamento, mas exige que você inclua métodos de contagem de referência ( refe deref) que você pode fazer subclassificando QSharedData. Como em grande parte do Qt, os objetos são mais bem utilizados por meio de uma ampla herança e subclasse de tudo parece ser o design pretendido.

QExplicitlySharedDataPointer- Muito parecido, QSharedDataPointerexceto que não implica implicitamente detach(). Eu chamaria essa versão 2.0 de QSharedDataPointerque esse pequeno aumento no controle sobre exatamente quando desanexar após a contagem de referência cair para zero não vale particularmente a pena um objeto totalmente novo.

QSharedPointer- Contagem de referência atômica, ponteiro seguro, ponteiro compartilhável, exclusões personalizadas (suporte a array), parece tudo o que um ponteiro inteligente deve ser. Isto é o que eu uso principalmente como um ponteiro inteligente no Qt e acho comparável com, boost:shared_ptrembora provavelmente significativamente mais sobrecarga, como muitos objetos no Qt.

QWeakPointer- Você sente um padrão recorrente? Assim como std::weak_ptre boost::weak_ptrisso é usado em conjunto com QSharedPointerquando você precisa de referências entre dois ponteiros inteligentes que, de outra forma, causariam a exclusão de seus objetos.

QScopedPointer- Esse nome também deve parecer familiar e, na verdade, foi baseado nas boost::scoped_ptrversões Qt de ponteiros compartilhados e fracos. Ele funciona para fornecer um ponteiro inteligente para um único proprietário, sem a sobrecarga, o QSharedPointerque o torna mais adequado para compatibilidade, código de exceção seguro e tudo o que você pode usar std::auto_ptrou boost::scoped_ptrpara.

AJG85
fonte
1
duas coisas que eu acho que vale a pena mencionar: intrusive_ptrnormalmente é mais eficiente do que shared_ptr, pois permite que você tenha uma única alocação de heap por objeto. shared_ptrno caso geral, alocará um pequeno objeto heap separado para os contadores de referência. std::make_sharedpode ser usado para obter o melhor dos dois mundos. shared_ptrcom apenas uma alocação de heap.
Arvid
Tenho uma pergunta talvez não relacionada: a coleta de lixo pode ser implementada apenas substituindo todos os ponteiros por shared_ptrs? (Não contando resolver referências cíclicos)
Seth Carnegie
@Seth Carnegie: Nem todos os indicadores apontarão para algo alocado na loja gratuita.
In silico
2
@the_mandrill Mas funciona se o destruidor da classe proprietária for definido em uma unidade de tradução separada (arquivo .cpp) do que o código do cliente, que no idioma Pimpl é fornecido de qualquer maneira. Como essa unidade de tradução geralmente conhece a definição completa do Pimpl e, portanto, seu destruidor (quando destrói o auto_ptr) destrói corretamente o Pimpl. Eu também tinha medo disso quando vi esses avisos, mas tentei e funcionou (o destruidor do Pimpl é chamado). PS .: use o @-syntax para que eu possa ver as respostas.
Christian Rau
2
A utilidade da lista foi aumentada adicionando links apropriados aos documentos.
ulidtko
5

Há também o Loki, que implementa indicadores inteligentes baseados em políticas.

Outras referências sobre ponteiros inteligentes baseados em políticas, abordando o problema do baixo suporte à otimização da base vazia, juntamente com a herança múltipla de muitos compiladores:

Gregory Pakosz
fonte
1

Além dos dados, também existem alguns orientados para a segurança:

SaferCPlusPlus

mse::TRefCountingPointeré uma referência que conta como ponteiro inteligente std::shared_ptr. A diferença é que mse::TRefCountingPointeré mais seguro, menor e mais rápido, mas não possui nenhum mecanismo de segurança de rosca. E vem nas versões "não nula" e "fixa" (não redirecionável) que podem ser assumidas com segurança como sempre apontando para um objeto alocado validamente. Então, basicamente, se o seu objeto de destino for compartilhado entre threads assíncronos, use std::shared_ptr, caso contrário, mse::TRefCountingPointeré mais ideal.

mse::TScopeOwnerPointeré semelhante a boost::scoped_ptr, mas funciona em conjunto com mse::TScopeFixedPointerum ponteiro "forte-fraco", como std::shared_ptre std::weak_ptr.

mse::TScopeFixedPointeraponta para objetos alocados na pilha ou cujo ponteiro "proprietário" está alocado na pilha. É (intencionalmente) limitado em sua funcionalidade para aprimorar a segurança em tempo de compilação sem custo de tempo de execução. O objetivo dos indicadores de "escopo" é essencialmente identificar um conjunto de circunstâncias simples e determinísticas o suficiente para que não sejam necessários mecanismos de segurança (tempo de execução).

mse::TRegisteredPointerse comporta como um ponteiro bruto, exceto que seu valor é definido automaticamente como null_ptr quando o objeto de destino é destruído. Pode ser usado como um substituto geral para ponteiros brutos na maioria das situações. Como um ponteiro bruto, ele não possui nenhuma segurança intrínseca de thread. Mas, em troca, não há problema em direcionar objetos alocados na pilha (e obter o benefício de desempenho correspondente). Quando as verificações em tempo de execução estão ativadas, esse ponteiro está protegido contra acesso a memória inválida. Como mse::TRegisteredPointerpossui o mesmo comportamento de um ponteiro bruto ao apontar para objetos válidos, ele pode ser "desabilitado" (substituído automaticamente pelo ponteiro bruto correspondente) por uma diretiva de tempo de compilação, permitindo que seja usado para ajudar a detectar erros na depuração / teste / beta, sem incorrer em custos indiretos no modo de liberação.

Aqui está um artigo descrevendo o porquê e como usá-los. (Observe, plugue descarado.)

Noé
fonte