Hay muchos punteros en C ++, pero para ser honesto en 5 años más o menos en la programación de C ++ (específicamente con Qt Framework), solo uso el viejo puntero sin formato:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
Sé que hay muchos otros punteros "inteligentes":
// shared pointer:
shared_ptr<SomeKindofObject> Object;
// unique pointer:
unique_ptr<SomeKindofObject> Object;
// weak pointer:
weak_ptr<SomeKindofObject> Object;
Pero no tengo la menor idea de qué hacer con ellos y qué pueden ofrecerme en comparación con los punteros en bruto.
Por ejemplo, tengo este encabezado de clase:
#ifndef LIBRARY
#define LIBRARY
class LIBRARY
{
public:
// Permanent list that will be updated from time to time where
// each items can be modified everywhere in the code:
QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings;
private:
// Temporary reader that will read something to put in the list
// and be quickly deleted:
QSettings *_reader;
// A dialog that will show something (just for the sake of example):
QDialog *_dialog;
};
#endif
Esto claramente no es exhaustivo, pero para cada uno de estos 3 punteros, ¿está bien dejarlos "en bruto" o debería usar algo más apropiado?
Y en la segunda vez, si un empleador leerá el código, ¿será estricto con el tipo de punteros que uso o no?
c++
programming-practices
pointers
qt
smart-pointer
CheshireChild
fuente
fuente
Respuestas:
Un puntero "en bruto" no está administrado. Es decir, la siguiente línea:
... 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ácilauto_ptr
perder memoria. Sin embargo, es suficiente para casos limitados como este: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<>
: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:A diferencia
auto_ptr<>
,unique_ptr<>
se comporta bien aquí, y cuando sevector
cambia el tamaño, ninguno de los objetos se eliminará accidentalmente mientras lasvector
copias se almacenan.shared_ptr
yweak_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 usastd::shared_ptr<>
: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 losshared_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 esdelete
d.Esto presenta un problema: las estructuras de doble enlace terminan con referencias circulares. Digamos que queremos agregar un
parent
puntero a nuestro árbolNode
: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<>
: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:
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
ymake_unique
Ahora, hay algunos problemas menores con
shared_ptr<>
yunique_ptr<>
que deben abordarse. Las siguientes dos líneas tienen un problema: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 proporcionastd::make_unique<>()
para resolver este problema: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 usuariodelete
.No sé cómo
QObject
se comportarán los mensajes de correo electrónico administrados por C ++ 11, por lo que no puedo decir queshared_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.fuente
shared_ptr
es un objeto separado, una asignación separada, delnew
objeto ed. Existen en diferentes lugares.make_shared
tiene la capacidad de juntarlos en la misma ubicación, lo que mejora la localidad de caché, entre otras cosas.shared_ptr
Es un objeto. Y para administrar un objeto, debe asignar un objeto (recuentos de referencia (débil + fuerte) + destructor).make_shared
permite asignar eso y el objeto administrado como una sola pieza.unique_ptr
no los usa, por lo que no hay una ventaja correspondiente, aparte de asegurarse de que el objeto siempre sea propiedad del puntero inteligente. Por otro lado, uno puede tener unshared_ptr
que posee un objeto subyacente y representa unnullptr
, o que no posee y representa un puntero no nulo.shared_ptr
hace: 1. Comparte la propiedad de algún objeto (representado por un objeto interno asignado dinámicamente que tiene un recuento de referencias débil y fuerte, así como un eliminador) . 2. Contiene un puntero. Esas dos partes son independientes.make_unique
ymake_shared
ambos se aseguran de que el objeto asignado se coloque de forma segura en un puntero inteligente. Además,make_shared
permite asignar el objeto de propiedad y el puntero administrado juntos.