Um ponteiro "bruto" não é gerenciado. Ou seja, a seguinte linha:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... vazará memória se um acompanhamento deletenão for executado no momento adequado.
auto_ptr
A fim de minimizar esses casos, std::auto_ptr<>foi introduzido. Devido às limitações do C ++ anteriores ao padrão de 2011, no entanto, ainda é muito fácil auto_ptrvazar memória. É suficiente para casos limitados, como este, no entanto:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Um de seus casos de uso mais fracos é em contêineres. Isso ocorre porque, se uma cópia de uma auto_ptr<>é feita e a cópia antiga não é redefinida com cuidado, o contêiner pode excluir o ponteiro e perder dados.
unique_ptr
Como substituição, o C ++ 11 introduziu std::unique_ptr<>:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Tal unique_ptr<>será limpo corretamente, mesmo quando for passado entre as funções. Ele faz isso representando semanticamente a "propriedade" do ponteiro - o "proprietário" a limpa. Isso o torna ideal para uso em contêineres:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Ao contrário auto_ptr<>, unique_ptr<>é bem-comportado aqui e, quando o vectorredimensionamento, nenhum dos objetos será excluído acidentalmente enquanto a vectorcópia é armazenada em backup.
shared_ptr e weak_ptr
unique_ptr<>é útil, com certeza, mas há casos em que você deseja que duas partes da sua base de código possam se referir ao mesmo objeto e copiar o ponteiro, mantendo a limpeza adequada garantida. Por exemplo, uma árvore pode ficar assim, ao usar std::shared_ptr<>:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Nesse caso, podemos até manter várias cópias de um nó raiz, e a árvore será limpa adequadamente quando todas as cópias do nó raiz forem destruídas.
Isso funciona porque cada um deles shared_ptr<>mantém não apenas o ponteiro para o objeto, mas também uma contagem de referência de todos os shared_ptr<>objetos que se referem ao mesmo ponteiro. Quando um novo é criado, a contagem aumenta. Quando um é destruído, a contagem diminui. Quando a contagem chega a zero, o ponteiro é deleted.
Portanto, isso apresenta um problema: Estruturas com ligações duplas terminam em referências circulares. Digamos que queremos adicionar um parentponteiro à nossa árvore Node:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Agora, se removermos um Node, há uma referência cíclica a ele. Nunca será deleted porque sua contagem de referência nunca será zero.
Para resolver esse problema, você usa um std::weak_ptr<>:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Agora, tudo funcionará corretamente e a remoção de um nó não deixará referências presas ao nó pai. No entanto, torna a caminhada na árvore um pouco mais complicada:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
Dessa forma, você pode bloquear uma referência ao nó e ter uma garantia razoável de que ele não desaparecerá enquanto você estiver trabalhando nele, já que está segurando shared_ptr<>nele.
make_shared e make_unique
Agora, existem alguns problemas menores shared_ptr<>e unique_ptr<>que devem ser resolvidos. As duas linhas a seguir têm um problema:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Se thrower()lançar uma exceção, as duas linhas vazarão memória. E mais do que isso, shared_ptr<>mantém a contagem de referência longe do objeto para o qual aponta e isso pode significar uma segunda alocação). Isso geralmente não é desejável.
O C ++ 11 fornece std::make_shared<>()e o C ++ 14 fornece std::make_unique<>()para resolver esse problema:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Agora, nos dois casos, mesmo se thrower()lançar uma exceção, não haverá vazamento de memória. Como bônus, make_shared<>()tem a oportunidade de criar sua contagem de referência no mesmo espaço de memória que seu objeto gerenciado, que pode ser mais rápido e economizar alguns bytes de memória, oferecendo uma exceção de garantia de segurança!
Notas sobre Qt
Deve-se notar, no entanto, que o Qt, que deve suportar compiladores anteriores ao C ++ 11, possui seu próprio modelo de coleta de lixo: muitos QObjects possuem um mecanismo no qual eles serão destruídos corretamente sem a necessidade do usuário para deleteeles.
Não sei como QObjectse comportará quando gerenciado por ponteiros gerenciados pelo C ++ 11, portanto não posso dizer que shared_ptr<QDialog>é uma boa idéia. Não tenho experiência suficiente com o Qt para ter certeza, mas acredito que o Qt5 foi ajustado para este caso de uso.