Tengo problemas para entender el uso de punteros inteligentes como miembros de clase en C ++ 11. He leído mucho sobre punteros inteligentes y creo que entiendo cómo unique_ptr
y shared_ptr
/ o weak_ptr
trabajo en general. Lo que no entiendo es el uso real. Parece que todo el mundo recomienda usarlo unique_ptr
como el camino a seguir casi todo el tiempo. Pero, ¿cómo implementaría algo como esto?
class Device {
};
class Settings {
Device *device;
public:
Settings(Device *device) {
this->device = device;
}
Device *getDevice() {
return device;
}
};
int main() {
Device *device = new Device();
Settings settings(device);
// ...
Device *myDevice = settings.getDevice();
// do something with myDevice...
}
Digamos que me gustaría reemplazar los punteros con punteros inteligentes. A unique_ptr
no funcionaría por getDevice()
, ¿verdad? ¿Entonces es el momento en que uso shared_ptr
y weak_ptr
? No hay forma de usar unique_ptr
? Me parece que para la mayoría de los casos shared_ptr
tiene más sentido a menos que esté usando un puntero en un alcance realmente pequeño.
class Device {
};
class Settings {
std::shared_ptr<Device> device;
public:
Settings(std::shared_ptr<Device> device) {
this->device = device;
}
std::weak_ptr<Device> getDevice() {
return device;
}
};
int main() {
std::shared_ptr<Device> device(new Device());
Settings settings(device);
// ...
std::weak_ptr<Device> myDevice = settings.getDevice();
// do something with myDevice...
}
¿Es ese el camino a seguir? ¡Muchas gracias!
fuente
device
al constructor desettings
, ¿desea poder seguir refiriéndose a él en el ámbito de la llamada, o solo a través desettings
? Si este último,unique_ptr
es útil. Además, ¿tiene un escenario donde el valor de retorno degetDevice()
esnull
. Si no, solo devuelva una referencia.shared_ptr
es correcto en 8/10 casos. Los otros 2/10 se dividen entreunique_ptr
yweak_ptr
. Además,weak_ptr
generalmente se usa para romper referencias circulares; No estoy seguro de que su uso se considere correcto.device
miembro de datos? Primero tienes que decidir eso.unique_ptr
lugar y ceder la propiedad al llamar al constructor, si sé que ya no lo necesitaré por ahora. Pero como diseñador de laSettings
clase, no sé si la persona que llama también quiere mantener una referencia. Tal vez el dispositivo se utilizará en muchos lugares. Ok, tal vez ese es exactamente tu punto. En ese caso, no sería el único propietario y es cuando usaría shared_ptr, supongo. Y: entonces los puntos inteligentes reemplazan los punteros, pero no las referencias, ¿verdad?Respuestas:
No, no necesariamente Lo importante aquí es determinar la política de propiedad adecuada para su
Device
objeto, es decir, quién será el propietario del objeto señalado por su puntero (inteligente).¿Será la instancia del
Settings
objeto solo ? ¿ElDevice
objeto tendrá que ser destruido automáticamente cuando elSettings
objeto sea destruido, o debería sobrevivir a ese objeto?En el primer caso,
std::unique_ptr
es lo que necesita, ya que esSettings
el único propietario (único) del objeto puntiagudo y el único objeto responsable de su destrucción.Bajo este supuesto,
getDevice()
debería devolver un puntero de observación simple (los punteros de observación son punteros que no mantienen vivo el objeto puntiagudo). El tipo más simple de puntero de observación es un puntero sin formato:[ NOTA 1: Tal vez se pregunte por qué estoy usando punteros sin procesar aquí, cuando todos siguen diciendo que los punteros sin procesar son malos, inseguros y peligrosos. En realidad, es una advertencia preciosa, pero es importante ponerla en el contexto correcto: los punteros sin formato son malos cuando se usan para realizar la gestión manual de la memoria , es decir, asignar y desasignar objetos a través de
new
ydelete
. Cuando se usa puramente como un medio para lograr una semántica de referencia y pasar punteros de observación no propietarios, no hay nada intrínsecamente peligroso en punteros sin formato, excepto tal vez por el hecho de que uno debe tener cuidado de no desreferenciar un puntero colgante. - NOTA FINAL 1 ][ NOTA 2: Como surgió en los comentarios, en este caso particular donde la propiedad es única y siempre se garantiza que el objeto poseído esté presente (es decir, el miembro de datos interno
device
nunca estaránullptr
), la funcióngetDevice()
podría (y tal vez debería) devuelve una referencia en lugar de un puntero. Si bien esto es cierto, decidí devolver un puntero sin procesar aquí porque quise decir que se trata de una respuesta corta que se podría generalizar al caso en el quedevice
podría estarnullptr
, y para mostrar que los punteros sin procesar están bien siempre que no se usen para Gestión manual de la memoria. - NOTA FINAL 2 ]La situación es radicalmente diferente, por supuesto, si su
Settings
objeto no debe tener la propiedad exclusiva del dispositivo. Este podría ser el caso, por ejemplo, si la destrucción delSettings
objeto no implica también la destrucción delDevice
objeto puntiagudo .Esto es algo que solo usted como diseñador de su programa puede decir; Según el ejemplo que proporcione, es difícil para mí saber si este es el caso o no.
Para ayudarlo a resolverlo, puede preguntarse si hay otros objetos además de los
Settings
que tienen derecho a mantenerDevice
vivo el objeto siempre que lo apunten, en lugar de ser solo observadores pasivos. Si ese es el caso, entonces necesita una política de propiedad compartida , que es lo questd::shared_ptr
ofrece:Tenga en cuenta que
weak_ptr
es un puntero de observación , no un puntero propietario; en otras palabras, no mantiene vivo el objeto puntiagudo si todos los demás punteros propietarios del objeto puntiagudo quedan fuera de alcance.La ventaja de
weak_ptr
un puntero sin formato regular es que puede saber con seguridad siweak_ptr
está colgando o no (es decir, si está apuntando a un objeto válido o si el objeto al que apunta originalmente se ha destruido). Esto se puede hacer llamando a laexpired()
función miembro en elweak_ptr
objeto.fuente
weak_ptr
es siempre una alternativa a los punteros de observación sin procesar. Es más seguro en cierto sentido, porque podría verificar si está colgando antes de desreferenciarlo, pero también viene con algo de sobrecarga. Si puede garantizar fácilmente que no va a desreferenciar un puntero colgante, entonces debería estar bien observando punteros en brutogetDevice()
devuelva una referencia, ¿no? Entonces la persona que llama no tendría que verificarnullptr
.auto myDevice = settings.getDevice()
creará una nueva instancia de tipoDevice
llamadamyDevice
y la copiará-construirá a partir de la referenciada por la referencia quegetDevice()
devuelve. Si quieresmyDevice
ser una referencia, debes hacerloauto& myDevice = settings.getDevice()
. Entonces, a menos que me falte algo, estamos de vuelta en la misma situación que tuvimos sin usarauto
.unique_ptr
a un cliente abre la posibilidad de que el cliente se mude de él, adquiriendo la propiedad y dejándolo con un puntero nulo (único).const_cast
), personalmente no lo haría. Expone un detalle de implementación, es decir, el hecho de que la propiedad es única y se realiza a través de aunique_ptr
. Veo las cosas de esta manera: si desea / necesita pasar / devolver la propiedad, pase / devuelva un puntero inteligente (unique_ptr
oshared_ptr
, dependiendo del tipo de propiedad). Si no desea / necesita pasar / devolver la propiedad, use unconst
puntero o referencia (debidamente calificado), principalmente dependiendo de si el argumento puede ser nulo o no.week_ptr
se usa solo para bucles de referencia. El gráfico de dependencia debe ser un gráfico dirigido acíclico. En los punteros compartidos hay 2 recuentos de referencia: 1 parashared_ptr
sy 1 para todos los punteros (shared_ptr
yweak_ptr
). Cuandoshared_ptr
se eliminan todos los s, se elimina el puntero. Cuando se necesita un punteroweak_ptr
,lock
debe usarse para obtener el puntero, si existe.fuente
shared_ptr
? ¿Puedes explicar por qué? Por lo que entiendo,weak_ptr
no tiene que contarse porque simplemente crea uno nuevoshared_ptr
cuando se opera en el objeto (si el objeto subyacente todavía existe).lock
. La versión de refuerzo también es segura para subprocesos en el recuento de referencias (delete
se llama solo una vez).weak_ptr::lock()
saber si el objeto ha expirado, debe inspeccionar el "bloque de control" que contiene el primer recuento de referencias y el puntero al objeto, por lo que el bloque de control no debe destruirse mientras todavía hayaweak_ptr
objetos en uso, entonces el número deweak_ptr
objetos debe ser rastreado, que es lo que hace el segundo recuento de referencia. El objeto se destruye cuando la primera cuenta de referencia cae a cero, el bloque de control se destruye cuando la segunda cuenta de referencia cae a cero.