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.
fonte
osg::ref_ptr
.std::auto_ptr
.std::auto_ptr_ref
é um detalhe de design destd::auto_ptr
.std::auto_ptr
nã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_ptr
só 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.auto_ptr_ref
ser 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.Respostas:
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 exigedelete
destruiçã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 conjuntostd::auto_ptr
para permitir a cópia e a atribuição em determinadas situações. Especificamente, ele pode ser usado para converter um não-conststd::auto_ptr
em 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_ptr
nã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 destd::auto_ptr
trabalhar 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 eboost::shared_ptr
melhorou 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_shared
pode 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 eboost::weak_ptr
. Esta é uma referência a um objeto de propriedade de astd::shared_ptr
e, portanto, não impedirá a exclusão do objeto se astd::shared_ptr
contagem de referência cair para zero. Para obter acesso ao ponteiro bruto, você primeiro precisará acessar ostd::shared_ptr
chamando,lock
que retornará um vaziostd::shared_ptr
se 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 destd::weak_ptr
, com base nesta implementação, isso permite uma referência não proprietária aboost::shared_ptr
. Você não chama surpreendentementelock()
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 eboost::shared_ptr
quando utilizável. É comparável,std::auto_ptr
principalmente 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_ptr
normalmente é mais eficiente do que,shared_ptr
pois permite que você tenha uma única alocação de heap por objeto. (obrigado Arvid)boost::shared_array
- Isto éboost::shared_ptr
para matrizes. Basicamentenew []
,operator[]
e,delete []
é claro, são assados. Isso pode ser usado em contêineres STL e, tanto quanto sei, tudoboost:shared_ptr
faz, embora você não possa usáboost::weak_ptr
-los. No entanto, você pode usar alternativamente umboost::shared_ptr<std::vector<>>
para funcionalidade semelhante e recuperar a capacidade de usarboost::weak_ptr
para referências.boost::scoped_array
- Isto éboost::scoped_ptr
para matrizes. Assim como acontece comboost::shared_array
toda 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 usarstd::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, considereboost::array
.Qt
QPointer
- Introduzido no Qt 4.0, este é um ponteiro inteligente "fraco", que só funciona comQObject
classes 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_ptr
embora tenha alguns recursos de segurança de encadeamento, mas exige que você inclua métodos de contagem de referência (ref
ederef
) que você pode fazer subclassificandoQSharedData
. 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,QSharedDataPointer
exceto que não implica implicitamentedetach()
. Eu chamaria essa versão 2.0 deQSharedDataPointer
que 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_ptr
embora provavelmente significativamente mais sobrecarga, como muitos objetos no Qt.QWeakPointer
- Você sente um padrão recorrente? Assim comostd::weak_ptr
eboost::weak_ptr
isso é usado em conjunto comQSharedPointer
quando 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 nasboost::scoped_ptr
versões Qt de ponteiros compartilhados e fracos. Ele funciona para fornecer um ponteiro inteligente para um único proprietário, sem a sobrecarga, oQSharedPointer
que o torna mais adequado para compatibilidade, código de exceção seguro e tudo o que você pode usarstd::auto_ptr
ouboost::scoped_ptr
para.fonte
intrusive_ptr
normalmente é mais eficiente do queshared_ptr
, pois permite que você tenha uma única alocação de heap por objeto.shared_ptr
no caso geral, alocará um pequeno objeto heap separado para os contadores de referência.std::make_shared
pode ser usado para obter o melhor dos dois mundos.shared_ptr
com apenas uma alocação de heap.shared_ptr
s? (Não contando resolver referências cíclicos)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:
fonte
Além dos dados, também existem alguns orientados para a segurança:
SaferCPlusPlus
mse::TRefCountingPointer
é uma referência que conta como ponteiro inteligentestd::shared_ptr
. A diferença é quemse::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, usestd::shared_ptr
, caso contrário,mse::TRefCountingPointer
é mais ideal.mse::TScopeOwnerPointer
é semelhante aboost::scoped_ptr
, mas funciona em conjunto commse::TScopeFixedPointer
um ponteiro "forte-fraco", comostd::shared_ptr
estd::weak_ptr
.mse::TScopeFixedPointer
aponta 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::TRegisteredPointer
se 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. Comomse::TRegisteredPointer
possui 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.)
fonte