Punteros inteligentes: ¿quién es el propietario del objeto? [cerrado]

114

C ++ tiene que ver con la propiedad de la memoria, también conocida como semántica de propiedad .

Es responsabilidad del propietario de una parte de la memoria asignada dinámicamente liberar esa memoria. Entonces, la pregunta realmente es a quién pertenece la memoria.

En C ++, la propiedad está documentada por el tipo en el que se envuelve un puntero sin formato, por lo que en un buen programa (IMO) C ++ es muy raro ( raro , no nunca ) ver punteros sin procesar (ya que los punteros sin formato no tienen propiedad inferida, por lo que podemos no decir quién es el propietario de la memoria y, por lo tanto, sin una lectura cuidadosa de la documentación, no puede decir quién es el responsable de la propiedad).

Por el contrario, es raro ver punteros sin formato almacenados en una clase, cada puntero sin formato se almacena dentro de su propio contenedor de puntero inteligente. ( Nota: si no posee un objeto, no debería almacenarlo porque no puede saber cuándo saldrá del alcance y será destruido).

Entonces la pregunta:

  • ¿Qué tipo de semántica de propiedad ha encontrado la gente?
  • ¿Qué clases estándar se utilizan para implementar esa semántica?
  • ¿En qué situaciones los encuentra útiles?

Mantengamos 1 tipo de propiedad semántica por respuesta para que se puedan votar hacia arriba y hacia abajo individualmente.

Resumen:

Conceptualmente, los punteros inteligentes son simples y una implementación ingenua es fácil. He visto muchos intentos de implementación, pero invariablemente se rompen de alguna manera que no es obvia para el uso casual y los ejemplos. Por lo tanto, recomiendo usar siempre punteros inteligentes bien probados de una biblioteca en lugar de usar los suyos propios. std::auto_ptro uno de los indicadores inteligentes de Boost parece cubrir todas mis necesidades.

std::auto_ptr<T>:

Una sola persona es propietaria del objeto. Se permite la transferencia de propiedad.

Uso: esto le permite definir interfaces que muestran la transferencia explícita de propiedad.

boost::scoped_ptr<T>

Una sola persona es propietaria del objeto. NO se permite la transferencia de propiedad.

Uso: se utiliza para mostrar propiedad explícita. El objeto será destruido por el destructor o cuando se restablezca explícitamente.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Propiedad múltiple. Este es un puntero contado de referencia simple. Cuando el recuento de referencia llega a cero, el objeto se destruye.

Uso: cuando un objeto puede tener varias flores con una vida útil que no se puede determinar en el momento de la compilación.

boost::weak_ptr<T>:

Se usa con shared_ptr<T>en situaciones en las que puede ocurrir un ciclo de punteros.

Uso: Se utiliza para evitar que los ciclos retengan objetos cuando solo el ciclo mantiene un recuento de referencia compartido.

Martin York
fuente
14
?? ¿Cual era la pregunta?
Pacerier
9
Solo quería señalar que desde que se publicó esta pregunta, auto_ptr ha quedado obsoleto en favor de (el ahora estandarizado) unique_ptr
Juan Campa
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) ¿Se puede reformular esto? No lo entiendo en absoluto.
lolololol ol
@lololololol Cortaste la oración a la mitad. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. Los punteros RAW no tienen semántica de propiedad. Si no conoce al propietario, no sabe quién es responsable de eliminar el objeto. Hay varias clases estándar que se utilizan para ajustar punteros (std :: shared_ptr, std :: unique_ptr, etc.) que definen la propiedad y, por lo tanto, defina quién es responsable de eliminar el puntero.
Martin York
1
En C ++ 11 + ¡NO USE auto_ptr! ¡Utilice unique_ptr en su lugar!
val dice Reincorporar a Monica

Respuestas:

20

Para mí, estos 3 tipos cubren la mayoría de mis necesidades:

shared_ptr - referencia contada, desasignación cuando el contador llega a cero

weak_ptr- igual que el anterior, pero es un 'esclavo' para un shared_ptr, no se puede desasignar

auto_ptr- cuando la creación y la desasignación ocurren dentro de la misma función, o cuando el objeto debe considerarse de un solo propietario. Cuando asigna un puntero a otro, el segundo 'roba' el objeto del primero.

Tengo mi propia implementación para estos, pero también están disponibles en formato Boost.

Todavía paso objetos por referencia ( constsiempre que sea posible), en este caso, el método llamado debe asumir que el objeto está vivo solo durante el tiempo de la llamada.

Hay otro tipo de puntero que uso al que llamo hub_ptr . Es cuando tiene un objeto que debe ser accesible desde objetos anidados en él (generalmente como una clase base virtual). Esto podría resolverse pasándoles un weak_ptra, pero no tiene un shared_ptra sí mismo. Como sabe que estos objetos no vivirían más que él, les pasa un hub_ptr (es solo un contenedor de plantilla para un puntero normal).

Fabio Ceconello
fuente
2
En lugar de crear su propia clase de puntero (hub_ptr), ¿por qué no pasa * esto a estos objetos y deja que lo almacenen como referencia? Dado que incluso reconoce que los objetos serán destruidos al mismo tiempo que la clase propietaria, no entiendo el punto de saltar a través de tantos obstáculos.
Michel
4
Básicamente es un contrato de diseño para dejar las cosas claras. Cuando el objeto secundario recibe hub_ptr, sabe que el objeto puntiagudo no se destruirá durante la vida del niño y no tiene propiedad sobre él. Tanto el objeto contenido como el contenedor están de acuerdo con un conjunto claro de reglas. Si usa un puntero desnudo, las reglas se pueden documentar, pero el compilador y el código no las harán cumplir.
Fabio Ceconello
1
También tenga en cuenta que puede tener #ifdefs para hacer que hub_ptr se defina como un puntero desnudo en las compilaciones de lanzamiento, por lo que la sobrecarga existirá solo en la compilación de depuración.
Fabio Ceconello
3
Tenga en cuenta que la documentación de Boost contradice su descripción de scoped_ptr. Afirma que lo es noncopyabley que la propiedad no se puede transferir.
Alec Thomas
3
@ Alec Thomas, tienes razón. Estaba pensando en auto_ptr y escribí scoped_ptr. Corregido.
Fabio Ceconello
23

Modelo C ++ simple

En la mayoría de los módulos que vi, de forma predeterminada, se asumió que recibir punteros no estaba recibiendo propiedad. De hecho, las funciones / métodos que abandonaron la propiedad de un puntero fueron muy raros y expresaron explícitamente ese hecho en su documentación.

Este modelo asume que el usuario es propietario solo de lo que asigna explícitamente . Todo lo demás se elimina automáticamente (en la salida del osciloscopio o mediante RAII). Este es un modelo similar a C, ampliado por el hecho de que la mayoría de los punteros son propiedad de objetos que los desasignarán automáticamente o cuando sea necesario (en la mayoría de los casos, en la destrucción de dichos objetos), y que la duración de vida de los objetos es predecible (RAII es su amigo, de nuevo).

En este modelo, los punteros sin procesar circulan libremente y en su mayoría no son peligrosos (pero si el desarrollador es lo suficientemente inteligente, usará referencias siempre que sea posible).

  • punteros crudos
  • std :: auto_ptr
  • boost :: scoped_ptr

Modelo C ++ con punta inteligente

En un código lleno de punteros inteligentes, el usuario puede esperar ignorar la vida útil de los objetos. El propietario nunca es el código de usuario: es el puntero inteligente en sí mismo (RAII, nuevamente). El problema es que las referencias circulares mezcladas con punteros inteligentes contados por referencia pueden ser mortales , por lo que debe lidiar tanto con punteros compartidos como con punteros débiles. Por lo tanto, todavía tiene que considerar la propiedad (el puntero débil bien podría apuntar a nada, incluso si su ventaja sobre el puntero sin formato es que puede decírselo).

  • boost :: shared_ptr
  • impulso :: debil_ptr

Conclusión

Independientemente de los modelos que describa, a menos que sea una excepción, recibir un puntero no es recibir su propiedad y sigue siendo muy importante saber quién posee a quién . Incluso para el código C ++ que usa mucho referencias y / o punteros inteligentes.

paercebal
fuente
10

No tengo propiedad compartida. Si lo hace, asegúrese de que sea solo con el código que no controla.

Eso resuelve el 100% de los problemas, ya que te obliga a entender cómo interactúa todo.

MSN
fuente
2
  • Propiedad compartida
  • boost :: shared_ptr

Cuando un recurso se comparte entre varios objetos. El boost shared_ptr usa el recuento de referencias para asegurarse de que el recurso sea desasignado cuando todos hayan terminado.

Martin York
fuente
2

std::tr1::shared_ptr<Blah> es a menudo su mejor apuesta.

Matt Cruikshank
fuente
2
shared_ptr es el más común. Pero hay muchos más. Cada uno tiene su propio patrón de uso y lugares buenos y malos para demandar. Un poco más de descripción estaría bien.
Martin York
Si está atascado con un compilador más antiguo, boost :: shared_ptr <blah> es en lo que se basa std :: tr1 :: shared_ptr <blah>. Es una clase lo suficientemente simple que probablemente puedas extraerla de Boost y usarla incluso si tu compilador no es compatible con la última versión de Boost.
Branan
2

Desde boost, también está la biblioteca de contenedores de punteros . Estos son un poco más eficientes y fáciles de usar que un contenedor estándar de punteros inteligentes, si solo usará los objetos en el contexto de su contenedor.

En Windows, existen los punteros COM (IUnknown, IDispatch y amigos) y varios punteros inteligentes para manejarlos (por ejemplo, CComPtr de ATL y los punteros inteligentes generados automáticamente por la instrucción "import" en Visual Studio basada en la clase _com_ptr ).

Ryan Ginstrom
fuente
1
  • Un dueño
  • boost :: scoped_ptr

Cuando necesita asignar memoria de forma dinámica pero quiere asegurarse de que se desasigne en cada punto de salida del bloque.

Encuentro esto útil, ya que se puede volver a colocar y liberar fácilmente sin tener que preocuparme por una fuga.

Pieter
fuente
1

Creo que nunca estuve en condiciones de compartir la propiedad de mi diseño. De hecho, desde lo alto de mi cabeza, el único caso válido en el que puedo pensar es el patrón Flyweight.

Nemanja Trifunovic
fuente
1

yasper :: ptr es una alternativa ligera de boost :: shared_ptr. Funciona bien en mi (por ahora) pequeño proyecto.

En la página web en http://yasper.sourceforge.net/ se describe de la siguiente manera:

¿Por qué escribir otro puntero inteligente de C ++? Ya existen varias implementaciones de punteros inteligentes de alta calidad para C ++, entre las que destaca el panteón de punteros Boost y el SmartPtr de Loki. Para una buena comparación de las implementaciones de punteros inteligentes y cuando su uso sea apropiado, lea The New C ++: Smart (er) Pointers de Herb Sutter. En contraste con las características expansivas de otras bibliotecas, Yasper es un puntero de conteo de referencias de enfoque estrecho. Se corresponde estrechamente con las políticas shared_ptr de Boost y RefCounted / AllowConversion de Loki. Yasper permite a los programadores de C ++ olvidarse de la gestión de la memoria sin introducir las grandes dependencias de Boost o tener que aprender sobre las complicadas plantillas de políticas de Loki. Filosofía

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

El último punto puede ser peligroso, ya que yasper permite acciones riesgosas (pero útiles) (como la asignación a punteros sin procesar y la liberación manual) no permitidas por otras implementaciones. ¡Tenga cuidado, use esas funciones solo si sabe lo que está haciendo!

Hernán
fuente
1

Existe otra forma de propietario único transferible que se usa con frecuencia, y es preferible auto_ptrporque evita los problemas causados ​​por auto_ptrla loca corrupción de la semántica de asignación.

No hablo de otro que swap. Cualquier tipo con una swapfunción adecuada puede concebirse como una referencia inteligente a algún contenido, que es de su propiedad hasta el momento en que la propiedad se transfiere a otra instancia del mismo tipo, intercambiándolos. Cada instancia conserva su identidad pero está vinculada a contenido nuevo. Es como una referencia que se puede volver a enlazar con seguridad.

(Es una referencia inteligente en lugar de un puntero inteligente porque no es necesario eliminarla explícitamente para acceder al contenido).

Esto significa que auto_ptr se vuelve menos necesario; solo se necesita para llenar los espacios donde los tipos no tienen una buena swapfunción. Pero todos los contenedores estándar lo hacen.

Daniel Earwicker
fuente
Tal vez se vuelva menos necesario (yo diría que scoped_ptr lo hace menos necesario que esto), pero no desaparecerá. Tener una función de intercambio no te ayuda en absoluto si asignas algo al montón y alguien lo lanza antes de que lo elimines, o simplemente lo olvidas.
Michel
Eso es exactamente lo que dije en el último párrafo.
Daniel Earwicker
0
  • Un propietario: también conocido como eliminar en la copia
  • std :: auto_ptr

Cuando el creador del objeto quiere ceder explícitamente la propiedad a otra persona. Esta es también una forma de documentar en el código que le estoy dando y ya no lo estoy rastreando, así que asegúrese de eliminarlo cuando haya terminado.

Martin York
fuente