Un puntero "en bruto" no está administrado. Es decir, la siguiente línea:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... perderá memoria si delete
no se ejecuta un acompañamiento en el momento adecuado.
auto_ptr
Con el fin de minimizar estos casos, std::auto_ptr<>
se introdujo. Sin embargo, debido a las limitaciones de C ++ anteriores al estándar de 2011, aún es muy fácil auto_ptr
perder memoria. Sin embargo, es suficiente para casos limitados como este:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Uno de sus casos de uso más débiles es en contenedores. Esto se debe a que si se realiza una copia de un auto_ptr<>
y la copia anterior no se restablece cuidadosamente, el contenedor puede eliminar el puntero y perder datos.
unique_ptr
Como reemplazo, C ++ 11 introdujo 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<>
se limpiará correctamente, incluso cuando se pasa entre funciones. Lo hace representando semánticamente la "propiedad" del puntero: el "propietario" lo limpia. Esto lo hace ideal para usar en contenedores:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
A diferencia auto_ptr<>
, unique_ptr<>
se comporta bien aquí, y cuando se vector
cambia el tamaño, ninguno de los objetos se eliminará accidentalmente mientras las vector
copias se almacenan.
shared_ptr
y weak_ptr
unique_ptr<>
es útil, sin duda, pero hay casos en los que desea que dos partes de su base de código puedan hacer referencia al mismo objeto y copiar el puntero, sin dejar de garantizar la limpieza adecuada. Por ejemplo, un árbol podría verse así, cuando se usa std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
En este caso, incluso podemos conservar múltiples copias de un nodo raíz, y el árbol se limpiará correctamente cuando se destruyan todas las copias del nodo raíz.
Esto funciona porque cada uno shared_ptr<>
mantiene no solo el puntero al objeto, sino también un recuento de referencia de todos los shared_ptr<>
objetos que hacen referencia al mismo puntero. Cuando se crea uno nuevo, el recuento aumenta. Cuando uno es destruido, la cuenta baja. Cuando el recuento llega a cero, el puntero es delete
d.
Esto presenta un problema: las estructuras de doble enlace terminan con referencias circulares. Digamos que queremos agregar un parent
puntero a nuestro árbol 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;
};
Ahora, si eliminamos a Node
, hay una referencia cíclica a él. Nunca será delete
d porque su recuento de referencia nunca será cero.
Para resolver este problema, utiliza un 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;
};
Ahora, las cosas funcionarán correctamente y eliminar un nodo no dejará referencias atascadas en el nodo principal. Sin embargo, hacer que caminar por el árbol sea un poco más complicado:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
De esta manera, puede bloquear una referencia al nodo, y tiene una garantía razonable de que no desaparecerá mientras está trabajando en él, ya que está aferrándose a uno shared_ptr<>
de ellos.
make_shared
y make_unique
Ahora, hay algunos problemas menores con shared_ptr<>
y unique_ptr<>
que deben abordarse. Las siguientes dos líneas tienen un problema:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Si thrower()
arroja una excepción, ambas líneas perderán memoria. Y más que eso, shared_ptr<>
mantiene el recuento de referencia lejos del objeto al que apunta y esto puede significar una segunda asignación). Eso no suele ser deseable.
C ++ 11 proporciona std::make_shared<>()
y C ++ 14 proporciona std::make_unique<>()
para resolver este problema:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Ahora, en ambos casos, incluso si thrower()
arroja una excepción, no habrá una pérdida de memoria. Como beneficio adicional, make_shared<>()
tiene la oportunidad de crear su recuento de referencia en el mismo espacio de memoria que su objeto administrado, lo que puede ser más rápido y ahorrar algunos bytes de memoria, ¡al tiempo que le ofrece una garantía de seguridad excepcional!
Notas sobre Qt
Sin embargo, debe tenerse en cuenta que Qt, que debe ser compatible con compiladores anteriores a C ++ 11, tiene su propio modelo de recolección de basura: muchos QObject
tienen un mecanismo en el que serán destruidos adecuadamente sin la necesidad del usuario delete
.
No sé cómo QObject
se comportarán los mensajes de correo electrónico administrados por C ++ 11, por lo que no puedo decir que shared_ptr<QDialog>
sea una buena idea. No tengo suficiente experiencia con Qt para decirlo con certeza, pero creo que Qt5 se ha ajustado para este caso de uso.