¿Qué implementaciones de puntero inteligente C ++ están disponibles?

121

Comparaciones, ventajas, desventajas y cuándo usar

Este es un spin-off de un hilo de recolección de basura donde lo que pensé que era una respuesta simple generó muchos comentarios sobre algunas implementaciones específicas de puntero inteligente, por lo que parecía que valía la pena comenzar una nueva publicación.

En última instancia, la pregunta es ¿cuáles son las diversas implementaciones de punteros inteligentes en C ++ y cómo se comparan? Simplemente pros y contras o excepciones y trampas a algo que de otro modo podría funcionar.

Publiqué algunas implementaciones que usé o al menos pasé por alto y consideré usar como respuesta a continuación y entiendo sus diferencias y similitudes que pueden no ser 100% precisas, así que no dude en verificar o corregir los hechos según sea necesario.

El objetivo es aprender sobre algunos nuevos objetos y bibliotecas o corregir mi uso y comprensión de las implementaciones existentes que ya se usan ampliamente y terminar con una referencia decente para otros.

AJG85
fuente
55
Creo que esto debería volver a publicarse como respuesta a esta pregunta, y que la pregunta se convierta en una pregunta real. De lo contrario, siento que la gente cerrará esto como "no es una pregunta real".
extraño
3
Hay todo tipo de otros punteros inteligentes, por ejemplo, los punteros inteligentes ATL o OpenSceneGraphosg::ref_ptr .
James McNellis
11
¿Hay una pregunta aquí?
Cody Gray
66
Creo que has entendido mal std::auto_ptr. std::auto_ptr_refes un detalle de diseño de std::auto_ptr. std::auto_ptrno tiene nada que ver con la recolección de basura, su objetivo principal es específicamente permitir la transferencia segura de propiedad de excepciones, especialmente en situaciones de llamada a funciones y retorno de funciones. std::unique_ptrsolo puede resolver los "problemas" que cita con los contenedores estándar porque C ++ ha cambiado para permitir una distinción entre mover y copiar, y los contenedores estándar han cambiado para aprovechar esto.
CB Bailey
3
Dices que no eres un experto en punteros inteligentes, pero tu resumen es bastante exhaustivo y correcto (excepto por la pequeña objeción de auto_ptr_refser un detalle de implementación). Aún así, estoy de acuerdo en que debe publicar esto como respuesta y reformular la pregunta para que sea una pregunta real. Esto puede servir como referencia futura.
Konrad Rudolph

Respuestas:

231

C ++ 03

std::auto_ptr- Quizás uno de los originales sufría del síndrome del primer borrador que solo proporcionaba instalaciones limitadas de recolección de basura. El primer inconveniente es que recurre deletea la destrucción haciéndolos inaceptables para mantener objetos asignados a la matriz ( new[]). Toma posesión del puntero, por lo que dos punteros automáticos no deberían contener el mismo objeto. La asignación transferirá la propiedad y restablecerá el puntero automático rvalue a un puntero nulo. Lo que lleva quizás al peor inconveniente; no se pueden usar dentro de contenedores STL debido a la incapacidad antes mencionada de ser copiados. El golpe final para cualquier caso de uso es que están programados para ser obsoletos en el próximo estándar de C ++.

std::auto_ptr_ref- Este no es un puntero inteligente, en realidad es un detalle de diseño utilizado en conjunto std::auto_ptrpara permitir la copia y asignación en ciertas situaciones. Específicamente, se puede usar para convertir un valor no constante std::auto_ptren un valor l utilizando el truco de Colvin-Gibbons, también conocido como un constructor de movimiento para transferir la propiedad.

Por el contrario, tal vez std::auto_ptrno estaba destinado a ser utilizado como un puntero inteligente de propósito general para la recolección automática de basura. La mayor parte de mi comprensión y suposiciones limitadas se basan en el uso efectivo de auto_ptr de Herb Sutter y lo uso regularmente, aunque no siempre de la manera más optimizada.


C ++ 11

std::unique_ptr- Este es nuestro amigo que será lo que sustituya std::auto_ptrserá bastante similar, excepto con las mejoras clave para corregir las debilidades de std::auto_ptrtrabajar con matrices, lvalue protección a través de constructor de copia privada, pudiendo utilizarse con contenedores STL y algoritmos, etc. Desde su sobrecarga de rendimiento y la huella de memoria es limitada, este es un candidato ideal para reemplazar, o quizás más acertadamente descrito como propietario, punteros en bruto. Como lo "único" implica que solo hay un propietario del puntero como el anterior std::auto_ptr.

std::shared_ptr- Creo que esto se basa en TR1 y se ha boost::shared_ptrmejorado para incluir alias y aritmética de punteros también. En resumen, envuelve un puntero inteligente contado de referencia alrededor de un objeto asignado dinámicamente. Como "compartido" implica que el puntero puede ser propiedad de más de un puntero compartido cuando la última referencia del último puntero compartido se sale del alcance, entonces el objeto se eliminará adecuadamente. Estos también son seguros para subprocesos y pueden manejar tipos incompletos en la mayoría de los casos. std::make_sharedse puede utilizar para construir de manera eficiente una std::shared_ptrasignación de un montón utilizando el asignador predeterminado.

std::weak_ptr- Del mismo modo basado en TR1 y boost::weak_ptr. Esta es una referencia a un objeto propiedad de ay, por std::shared_ptrlo tanto, no impedirá la eliminación del objeto si el std::shared_ptrrecuento de referencias cae a cero. Para obtener acceso al puntero sin formato, primero tendrá que acceder std::shared_ptrllamando llamando, locklo que devolverá un espacio vacío std::shared_ptrsi el puntero propiedad ha expirado y ya se ha destruido. Esto es principalmente útil para evitar recuentos de referencias colgantes indefinidos cuando se utilizan múltiples punteros inteligentes.


Aumentar

boost::shared_ptr- Probablemente el más fácil de usar en los escenarios más variados (STL, PIMPL, RAII, etc.) este es un puntero inteligente contado referenciado compartido. He escuchado algunas quejas sobre el rendimiento y los gastos generales en algunas situaciones, pero debo haberlas ignorado porque no puedo recordar cuál fue el argumento. Aparentemente, era lo suficientemente popular como para convertirse en un objeto C ++ estándar pendiente y no se me ocurren inconvenientes sobre la norma con respecto a los punteros inteligentes.

boost::weak_ptr- Al igual que la descripción anterior de std::weak_ptr, basada en esta implementación, esto permite una referencia no propietaria a a boost::shared_ptr. No es sorprendente que llame lock()para acceder al puntero compartido "fuerte" y debe verificar para asegurarse de que sea válido, ya que podría haber sido destruido. Solo asegúrate de no almacenar el puntero compartido devuelto y déjalo fuera de alcance tan pronto como hayas terminado con él; de lo contrario, volverás al problema de referencia cíclica donde tus recuentos de referencia se colgarán y los objetos no se destruirán.

boost::scoped_ptr- Esta es una clase de puntero inteligente simple con poca sobrecarga, probablemente diseñada para una mejor alternativa de rendimiento boost::shared_ptrcuando sea utilizable. Es comparable std::auto_ptrespecialmente al hecho de que no se puede usar de forma segura como un elemento de un contenedor STL o con múltiples punteros al mismo objeto.

boost::intrusive_ptr- Nunca he usado esto, pero tengo entendido que está diseñado para usarse al crear sus propias clases compatibles con punteros inteligentes. Debe implementar el recuento de referencias usted mismo, también deberá implementar algunos métodos si desea que su clase sea genérica, además, deberá implementar su propia seguridad de subprocesos. En el lado positivo, esto probablemente le brinda la forma más personalizada de elegir y elegir exactamente cuánto o qué "inteligencia" desea. intrusive_ptrnormalmente es más eficiente que shared_ptrya que le permite tener una única asignación de montón por objeto. (gracias Arvid)

boost::shared_array- Esto es boost::shared_ptrpara matrices. Básicamente new [], operator[]y por supuesto delete []están horneados. Esto se puede usar en contenedores STL y, por lo que sé, hace todo boost:shared_ptr, aunque no se puede usar boost::weak_ptrcon estos. Sin embargo, también puede usar a boost::shared_ptr<std::vector<>>para una funcionalidad similar y recuperar la capacidad de usar boost::weak_ptrpara referencias.

boost::scoped_array- Esto es boost::scoped_ptrpara matrices. Al igual que con boost::shared_arraytodas las bondades de matriz necesarias, esta no se puede copiar y, por lo tanto, no se puede usar en contenedores STL. He encontrado casi cualquier lugar en el que desees usar esto que probablemente puedas usar std::vector. Nunca he determinado cuál es realmente más rápido o tiene menos sobrecarga, pero esta matriz de ámbito parece mucho menos complicada que un vector STL. Cuando desee mantener la asignación en la pila, considere en su boost::arraylugar.


Qt

QPointer- Introducido en Qt 4.0, este es un puntero inteligente "débil" que solo funciona con QObjectclases derivadas, que en el marco de Qt es casi todo, así que eso no es realmente una limitación. Sin embargo, existen limitaciones, es decir, que no proporciona un puntero "fuerte" y, aunque puede verificar si el objeto subyacente es válido isNull(), puede encontrar que su objeto se destruye justo después de pasar esa verificación, especialmente en entornos de subprocesos múltiples. La gente considera q esto está en desuso, creo.

QSharedDataPointer- Este es un puntero inteligente "fuerte" potencialmente comparable, boost::intrusive_ptraunque tiene algo de seguridad de subproceso incorporado, pero requiere que incluyas métodos de conteo de referencia ( refy deref) que puedes hacer subclasificando QSharedData. Como con gran parte de Qt, los objetos se utilizan mejor a través de una amplia herencia y subclasificar todo parece ser el diseño previsto.

QExplicitlySharedDataPointer- Muy similar a QSharedDataPointerexcepto que no llama implícitamente detach(). Llamaría a esta versión 2.0, QSharedDataPointerya que ese ligero aumento en el control sobre exactamente cuándo separarse después de que el recuento de referencia caiga a cero no vale particularmente un objeto completamente nuevo.

QSharedPointer- Cuenta de referencia atómica, hilo seguro, puntero para compartir, eliminaciones personalizadas (soporte de matriz), suena como todo lo que debería ser un puntero inteligente. Esto es lo que uso principalmente como un puntero inteligente en Qt y lo encuentro comparable con, boost:shared_ptraunque probablemente significativamente más sobrecarga como muchos objetos en Qt.

QWeakPointer- ¿Sientes un patrón recurrente? Justo como std::weak_ptry boost::weak_ptresto se usa junto con QSharedPointercuando necesita referencias entre dos punteros inteligentes que de otra forma harían que sus objetos nunca se borren.

QScopedPointer- Este nombre también debería parecer familiar y, de hecho, se basó a boost::scoped_ptrdiferencia de las versiones Qt de punteros compartidos y débiles. Funciona para proporcionar un puntero inteligente de un solo propietario sin la sobrecarga de QSharedPointerlo que lo hace más adecuado para la compatibilidad, el código de excepción de seguridad y todas las cosas que puede usar std::auto_ptro boost::scoped_ptrpara las que puede usar .

AJG85
fuente
1
Creo que vale la pena mencionar dos cosas: intrusive_ptrnormalmente es más eficiente que shared_ptr, ya que le permite tener una única asignación de montón por objeto. shared_ptren el caso general, asignará un objeto de montón pequeño separado para los contadores de referencia. std::make_sharedse puede usar para obtener lo mejor de ambos mundos. shared_ptrcon solo una asignación de montón.
Arvid
Tengo una pregunta quizás no relacionada: ¿Se puede implementar la recolección de basura simplemente reemplazando todos los punteros por shared_ptrs? (Sin contar la resolución de referencias cíclicas)
Seth Carnegie
@Seth Carnegie: No todos los punteros apuntarán a algo asignado en la tienda gratuita.
En silico
2
@the_mandrill Pero funciona si el destructor de la clase propietaria se define en una unidad de traducción separada (archivo .cpp) que el código del cliente, que en el idioma Pimpl se da de todos modos. Debido a que esta unidad de traducción generalmente conoce la definición completa del Pimpl y, por lo tanto, su destructor (cuando destruye el auto_ptr) destruye correctamente el Pimpl. También tuve miedo de esto cuando vi esas advertencias, pero lo probé y funciona (se llama al destructor de Pimpl). PD .: utilice la sintaxis @ para ver las respuestas.
Christian Rau
2
La utilidad de la lista se incrementó al agregar enlaces apropiados a los documentos.
ulidtko
1

Además de los dados, también hay algunos orientados a la seguridad:

SaferCPlusPlus

mse::TRefCountingPointeres un puntero inteligente de recuento de referencias como std::shared_ptr. La diferencia es que mse::TRefCountingPointeres más seguro, más pequeño y más rápido, pero no tiene ningún mecanismo de seguridad de hilo. Y viene en versiones "no nulas" y "fijas" (no retargetables) que se puede suponer con seguridad que siempre apuntan a un objeto asignado de manera válida. Básicamente, si su objeto de destino se comparte entre subprocesos asincrónicos, use std::shared_ptr, de lo contrario, mse::TRefCountingPointeres más óptimo.

mse::TScopeOwnerPointeres similar a boost::scoped_ptr, pero funciona en conjunto con mse::TScopeFixedPointeruna relación de puntero "fuerte-débil" de tipo like std::shared_ptry std::weak_ptr.

mse::TScopeFixedPointerapunta a objetos que están asignados en la pila, o cuyo puntero "propietario" está asignado en la pila. Está (intencionalmente) limitado en su funcionalidad para mejorar la seguridad en tiempo de compilación sin costo de tiempo de ejecución. El objetivo de los indicadores de "alcance" es esencialmente identificar un conjunto de circunstancias que sean lo suficientemente simples y deterministas como para que no sean necesarios mecanismos de seguridad (tiempo de ejecución).

mse::TRegisteredPointerse comporta como un puntero sin formato, excepto que su valor se establece automáticamente en null_ptr cuando se destruye el objeto de destino. Se puede utilizar como un reemplazo general para punteros sin procesar en la mayoría de las situaciones. Al igual que un puntero en bruto, no tiene ninguna seguridad intrínseca de subprocesos. Pero a cambio no tiene ningún problema para apuntar a objetos asignados en la pila (y obtener el beneficio de rendimiento correspondiente). Cuando las comprobaciones en tiempo de ejecución están habilitadas, este puntero está seguro de acceder a memoria no válida. Debido a que mse::TRegisteredPointertiene el mismo comportamiento que un puntero sin procesar cuando apunta a objetos válidos, se puede "deshabilitar" (se reemplaza automáticamente con el puntero sin procesar correspondiente) con una directiva de tiempo de compilación, lo que permite utilizarlo para ayudar a detectar errores en la depuración / prueba / beta modos sin incurrir en gastos generales en el modo de lanzamiento.

Aquí hay un artículo que describe por qué y cómo usarlos. (Nota, enchufe descarado).

Noé
fuente