c++ - weak_ptr - Que tipo de ponteiro eu uso quando?




weak_ptr example c++ (3)

Casos de quando usar unique_ptr :

  • Métodos de fábrica
  • Membros que são ponteiros (pimpl incluído)
  • Armazenando ponteiros em contêineres stl (para evitar movimentos)
  • Uso de grandes objetos dinâmicos locais

Casos de quando usar shared_ptr :

  • Compartilhando objetos pelos encadeamentos
  • Compartilhando objetos em geral

Casos de quando usar weak_ptr :

  • Mapa grande que funciona como uma referência geral (ex. Um mapa de todos os sockets abertos)

Sinta-se à vontade para editar e adicionar mais

Ok, então a última vez que escrevi o C ++, std::auto_ptr era todo o std lib disponível, e boost::shared_ptr era toda a raiva. Eu nunca realmente olhei para o outro tipo de ponteiro inteligente fornecido. Eu entendo que o C ++ 11 agora fornece alguns dos tipos que surgiram, mas nem todos eles.

Alguém tem um algoritmo simples para determinar quando usar o ponteiro inteligente? De preferência, incluindo conselhos sobre ponteiros mudos (ponteiros crus como T* ) e o resto dos ponteiros inteligentes de impulso. (Algo como this seria ótimo).


Decidir qual ponteiro inteligente usar é uma questão de propriedade . Quando se trata de gerenciamento de recursos, o objeto A possui o objeto B se estiver no controle do tempo de vida do objeto B. Por exemplo, as variáveis ​​de membro são de propriedade de seus respectivos objetos porque o tempo de vida das variáveis ​​de membro está vinculado à vida útil do objeto. Você escolhe ponteiros inteligentes com base em como o objeto é de propriedade.

Observe que a propriedade em um sistema de software é separada da propriedade, como seria considerado fora do software. Por exemplo, uma pessoa pode "possuir" sua casa, mas isso não significa necessariamente que um objeto Person tenha controle sobre o tempo de vida de um objeto House . Confundir esses conceitos do mundo real com conceitos de software é uma maneira infalível de se programar em um buraco.

Se você tiver propriedade exclusiva do objeto, use std::unique_ptr<T> .

Se você compartilhou a propriedade do objeto ...
- Se não houver ciclos de propriedade, use std::shared_ptr<T> .
- Se houver ciclos, defina uma "direção" e use std::shared_ptr<T> em uma direção e std::weak_ptr<T> na outra.

Se o objeto possui você, mas há potencial de não ter dono, use ponteiros normais T* (por exemplo, ponteiros pai).

Se o objeto possui você (ou de outra forma tem existência garantida), use referências T& .

Advertência: esteja ciente dos custos dos ponteiros inteligentes. Em ambientes com memória ou desempenho limitado, pode ser benéfico usar apenas ponteiros normais com um esquema mais manual para gerenciar a memória.

Os custos:

  • Se você tiver um deleter personalizado (por exemplo, usar pools de alocação), isso implicará em uma sobrecarga por ponteiro, que pode ser facilmente evitada pela exclusão manual.
  • std::shared_ptr tem a sobrecarga de um incremento de contagem de referência na cópia, mais um decréscimo na destruição seguido por uma verificação de 0 contagens com a exclusão do objeto em retenção. Dependendo da implementação, isso pode aumentar seu código e causar problemas de desempenho.
  • Tempo de compilação. Como em todos os modelos, os ponteiros inteligentes contribuem negativamente para os tempos de compilação.

Exemplos:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

Uma árvore binária não possui seu pai, mas a existência de uma árvore implica a existência de seu pai (ou nullptr para raiz), de modo que usa um ponteiro normal. Uma árvore binária (com semântica de valor) possui propriedade exclusiva de seus filhos, então eles são std::unique_ptr .

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Aqui, o nó da lista possui suas listas seguinte e anterior, então definimos uma direção e usamos shared_ptr para next e weak_ptr para prev para quebrar o ciclo.


Propriedade compartilhada:
O shared_ptr e o weak_ptr adotados são praticamente os mesmos que os do Boost . Use-os quando precisar compartilhar um recurso e não souber qual deles será o último a estar vivo. Use weak_ptr para observar o recurso compartilhado sem influenciar seu tempo de vida, não para quebrar ciclos. Ciclos com shared_ptr normalmente não deveriam acontecer - dois recursos não podem possuir um ao outro.

Observe que o Boost oferece adicionalmente o shared_array , que pode ser uma alternativa adequada ao shared_ptr<std::vector<T> const> .

Em seguida, o Boost oferece intrusive_ptr , que são uma solução leve se o seu recurso já oferece gerenciamento de referência e você deseja adotá-lo para o princípio RAII. Este não foi adotado pelo padrão.

Propriedade única:
Boost também tem um scoped_ptr , que não é copiável e para o qual você não pode especificar um deleter. std::unique_ptr é boost::scoped_ptr em esteróides e deve ser sua escolha padrão quando você precisa de um ponteiro inteligente . Ele permite que você especifique um deletério em seus argumentos de modelo e é móvel , ao contrário de boost::scoped_ptr . Também é totalmente utilizável em contêineres STL, desde que você não use operações que precisam de tipos que podem ser copiados (obviamente).

Note novamente, que Boost tem uma versão de matriz: scoped_array , que o padrão unificado exigindo std::unique_ptr<T[]> especialização parcial que delete[] o ponteiro em vez de delete -lo (com o default_delete r). std::unique_ptr<T[]> também oferece operator[] vez de operator* e operator-> .

Observe que std::auto_ptr ainda está no padrão, mas está obsoleto . §D.10 [depr.auto.ptr]

O modelo de classe auto_ptr está obsoleto. [ Nota: O modelo de classe unique_ptr (20.7.1) fornece uma solução melhor. - end note ]

Nenhuma propriedade:
Use ponteiros mudos (ponteiros brutos) ou referências para referências não próprias a recursos e quando você souber que o recurso sobreviverá ao objeto / escopo de referência. Prefira referências e use ponteiros brutos quando precisar de capacidade de anulação ou de reajuste.

Se você quiser uma referência não proprietária a um recurso, mas não souber se o recurso sobreviverá ao objeto que faz referência a ele, empacote o recurso em um shared_ptr e use um weak_ptr - você pode testar se o pai shared_ptr está vivo com lock , que retornará um shared_ptr que não é nulo se o recurso ainda existir. Se quiser testar se o recurso está morto, use expired . Os dois podem parecer semelhantes, mas são muito diferentes em face da execução simultânea, pois o expired só garante seu valor de retorno para essa única instrução. Um teste aparentemente inocente como

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

é uma condição de corrida em potencial.







c++-faq