Um ponteiro "bruto" não é gerenciado. Ou seja, a seguinte linha:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... vazará memória se um acompanhamento delete
nã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_ptr
vazar 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 vector
redimensionamento, nenhum dos objetos será excluído acidentalmente enquanto a vector
có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 é delete
d.
Portanto, isso apresenta um problema: Estruturas com ligações duplas terminam em referências circulares. Digamos que queremos adicionar um parent
ponteiro à 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á delete
d 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 QObject
s possuem um mecanismo no qual eles serão destruídos corretamente sem a necessidade do usuário para delete
eles.
Não sei como QObject
se 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.