smart shared_ptr example c++ c++11 shared-ptr smart-pointers unique-ptr

c++ - smart - shared_ptr example



Uso de punteros inteligentes para los miembros de la clase (2)

Tengo problemas para entender el uso de punteros inteligentes como miembros de la clase en C ++ 11. He leído mucho sobre punteros inteligentes y creo que entiendo cómo funcionan unique_ptr y shared_ptr / weak_ptr en general. Lo que no entiendo es el uso real. Parece que todo el mundo recomienda usar 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. Un unique_ptr no funcionaría debido a getDevice() , ¿verdad? Entonces, ¿ese 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!


Un unique_ptr no funcionaría debido a getDevice() , ¿verdad?

No, no necesariamente Lo importante aquí es determinar la política de propiedad apropiada para su objeto Device , es decir, quién será el propietario del objeto señalado por su puntero (inteligente).

¿Va a ser la instancia del objeto Settings solo ? ¿ Device objeto Device se debe destruir automáticamente cuando se destruye el objeto Settings o debería sobrevivir ese objeto?

En el primer caso, std::unique_ptr es lo que necesita, ya que hace que Settings sea ​​el único propietario (único) del objeto señalado, y el único objeto que es responsable de su destrucción.

Bajo esta suposición, getDevice() debería devolver un simple puntero de observación (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:

#include <memory> class Device { }; class Settings { std::unique_ptr<Device> device; public: Settings(std::unique_ptr<Device> d) { device = std::move(d); } Device* getDevice() { return device.get(); } }; int main() { std::unique_ptr<Device> device(new Device()); Settings settings(std::move(device)); // ... Device *myDevice = settings.getDevice(); // do something with myDevice... }

[ NOTA 1: Usted se estará preguntando por qué estoy usando punteros sin procesar aquí, cuando todo el mundo sigue diciendo que los indicadores crudos son malos, inseguros y peligrosos. En realidad, es una advertencia preciosa, pero es importante ponerlo en el contexto correcto: los punteros crudos 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 y delete . Cuando se usa puramente como un medio para lograr la semántica de referencia y pasar desapercibido, observando punteros, no hay nada intrínsecamente peligroso en los punteros crudos, excepto tal vez por el hecho de que uno debe tener cuidado de no desviar un puntero colgante. - FIN NOTA 1 ]

[ NOTA 2: Como surgió en los comentarios, en este caso particular donde la propiedad es única y el objeto propiedad siempre está garantizado para estar presente (es decir, el device miembro de datos interno nunca va a ser nullptr ), la función getDevice() podría (y tal vez debería) devolver una referencia en lugar de un puntero. Si bien esto es cierto, decidí devolver un puntero sin procesar aquí porque quería decir que esta es una respuesta corta que podría generalizarse para el caso en que el device podría ser nullptr , y para mostrar que los punteros sin procesar están bien siempre y cuando uno no use ellos para la gestión de memoria manual. - FIN NOTA 2 ]

La situación es radicalmente diferente, por supuesto, si su objeto de Settings no debe tener la propiedad exclusiva del dispositivo. Este podría ser el caso, por ejemplo, si la destrucción del objeto de Settings no debe implicar la destrucción del objeto del Device apuntado también.

Esto es algo que solo usted como diseñador de su programa puede decir; del ejemplo que proporcionas, es difícil para mí decir si este es el caso o no.

Para ayudarlo a resolverlo, puede preguntarse si hay otros objetos aparte de Settings que tienen derecho a mantener vivo el objeto del Device siempre y cuando tengan un puntero a él, en lugar de ser solo observadores pasivos. Si ese es realmente el caso, entonces necesita una política de propiedad compartida , que es lo que ofrece std::shared_ptr :

#include <memory> class Device { }; class Settings { std::shared_ptr<Device> device; public: Settings(std::shared_ptr<Device> const& d) { device = d; } std::shared_ptr<Device> getDevice() { return device; } }; int main() { std::shared_ptr<Device> device = std::make_shared<Device>(); Settings settings(device); // ... std::shared_ptr<Device> myDevice = settings.getDevice(); // do something with myDevice... }

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 al objeto puntiagudo quedan fuera del alcance.

La ventaja de weak_ptr sobre un puntero weak_ptr sin weak_ptr es que se puede saber con seguridad si weak_ptr está colgando o no (es decir, si está apuntando a un objeto válido, o si el objeto al que se apunta originalmente ha sido destruido). Esto se puede hacer llamando a la función de miembro expired() en el objeto weak_ptr .


class Device { }; class Settings { std::shared_ptr<Device> device; public: Settings(const std::shared_ptr<Device>& device) : device(device) { } const std::shared_ptr<Device>& getDevice() { return device; } }; int main() { std::shared_ptr<Device> device(new Device()); Settings settings(device); // ... std::shared_ptr<Device> myDevice(settings.getDevice()); // do something with myDevice... return 0; }

week_ptr se usa solo para bucles de referencia. El gráfico de dependencia debe ser un gráfico acíclico dirigido. En punteros compartidos, hay 2 recuentos de referencia: 1 para shared_ptr s, y 1 para todos los punteros ( shared_ptr y weak_ptr ). Cuando se shared_ptr todos los shared_ptr s, se borra el puntero. Cuando se necesita puntero desde weak_ptr , se debe usar el lock para obtener el puntero, si existe.