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 da vida útil do objeto B. Por exemplo, as variáveis de membro pertencem aos 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 pertence.
Observe que a propriedade de um sistema de software é separada da propriedade, como poderíamos pensar fora do software. Por exemplo, uma pessoa pode "possuir" sua casa, mas isso não significa necessariamente que um Person
objeto tenha controle sobre a vida útil de um House
objeto. Confundir esses conceitos do mundo real com os conceitos de software é uma maneira infalível de se programar em um buraco.
Se você possui a propriedade exclusiva do objeto, use std::unique_ptr<T>
.
Se você compartilhou a propriedade do objeto ...
- Se não houver ciclos na 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 é seu, mas existe o potencial de não ter dono, use ponteiros normais T*
(por exemplo, ponteiros pai).
Se o objeto lhe pertence (ou tem existência garantida), use referências T&
.
Advertência: Esteja ciente dos custos de indicadores inteligentes. Em ambientes com desempenho limitado ou de memória, pode ser benéfico usar ponteiros normais com um esquema mais manual para gerenciar a memória.
Os custos:
- Se você possui um deleter personalizado (por exemplo, você usa pools de alocação), isso gera uma sobrecarga por ponteiro que pode ser facilmente evitada pela exclusão manual.
std::shared_ptr
possui a sobrecarga de um incremento da contagem de referência na cópia, além de um decréscimo na destruição seguido de uma verificação de contagem de 0 com exclusão do objeto em espera. Dependendo da implementação, isso pode inchar seu código e causar problemas de desempenho.
- Tempo de compilação. Como em todos os modelos, 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
raiz), de modo que usa um ponteiro normal. Uma árvore binária (com semântica de valores) possui propriedade exclusiva de seus filhos, de modo que 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 seguintes e anteriores, portanto, definimos uma direção e usamos shared_ptr
para next e weak_ptr
for prev para interromper o ciclo.