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.
fuente
osg::ref_ptr
.std::auto_ptr
.std::auto_ptr_ref
es un detalle de diseño destd::auto_ptr
.std::auto_ptr
no 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_ptr
solo 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.auto_ptr_ref
ser 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.Respuestas:
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 recurredelete
a 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 conjuntostd::auto_ptr
para permitir la copia y asignación en ciertas situaciones. Específicamente, se puede usar para convertir un valor no constantestd::auto_ptr
en 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_ptr
no 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 sustituyastd::auto_ptr
será bastante similar, excepto con las mejoras clave para corregir las debilidades destd::auto_ptr
trabajar 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 anteriorstd::auto_ptr
.std::shared_ptr
- Creo que esto se basa en TR1 y se haboost::shared_ptr
mejorado 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_shared
se puede utilizar para construir de manera eficiente unastd::shared_ptr
asignación de un montón utilizando el asignador predeterminado.std::weak_ptr
- Del mismo modo basado en TR1 yboost::weak_ptr
. Esta es una referencia a un objeto propiedad de ay, porstd::shared_ptr
lo tanto, no impedirá la eliminación del objeto si elstd::shared_ptr
recuento de referencias cae a cero. Para obtener acceso al puntero sin formato, primero tendrá que accederstd::shared_ptr
llamando llamando,lock
lo que devolverá un espacio vacíostd::shared_ptr
si 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 destd::weak_ptr
, basada en esta implementación, esto permite una referencia no propietaria a aboost::shared_ptr
. No es sorprendente que llamelock()
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 rendimientoboost::shared_ptr
cuando sea utilizable. Es comparablestd::auto_ptr
especialmente 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_ptr
normalmente es más eficiente queshared_ptr
ya que le permite tener una única asignación de montón por objeto. (gracias Arvid)boost::shared_array
- Esto esboost::shared_ptr
para matrices. Básicamentenew []
,operator[]
y por supuestodelete []
están horneados. Esto se puede usar en contenedores STL y, por lo que sé, hace todoboost:shared_ptr
, aunque no se puede usarboost::weak_ptr
con estos. Sin embargo, también puede usar aboost::shared_ptr<std::vector<>>
para una funcionalidad similar y recuperar la capacidad de usarboost::weak_ptr
para referencias.boost::scoped_array
- Esto esboost::scoped_ptr
para matrices. Al igual que conboost::shared_array
todas 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 usarstd::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 suboost::array
lugar.Qt
QPointer
- Introducido en Qt 4.0, este es un puntero inteligente "débil" que solo funciona conQObject
clases 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álidoisNull()
, 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_ptr
aunque tiene algo de seguridad de subproceso incorporado, pero requiere que incluyas métodos de conteo de referencia (ref
yderef
) que puedes hacer subclasificandoQSharedData
. 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 aQSharedDataPointer
excepto que no llama implícitamentedetach()
. Llamaría a esta versión 2.0,QSharedDataPointer
ya 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_ptr
aunque probablemente significativamente más sobrecarga como muchos objetos en Qt.QWeakPointer
- ¿Sientes un patrón recurrente? Justo comostd::weak_ptr
yboost::weak_ptr
esto se usa junto conQSharedPointer
cuando 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ó aboost::scoped_ptr
diferencia de las versiones Qt de punteros compartidos y débiles. Funciona para proporcionar un puntero inteligente de un solo propietario sin la sobrecarga deQSharedPointer
lo que lo hace más adecuado para la compatibilidad, el código de excepción de seguridad y todas las cosas que puede usarstd::auto_ptr
oboost::scoped_ptr
para las que puede usar .fuente
intrusive_ptr
normalmente es más eficiente queshared_ptr
, ya que le permite tener una única asignación de montón por objeto.shared_ptr
en el caso general, asignará un objeto de montón pequeño separado para los contadores de referencia.std::make_shared
se puede usar para obtener lo mejor de ambos mundos.shared_ptr
con solo una asignación de montón.shared_ptr
s? (Sin contar la resolución de referencias cíclicas)También está Loki, que implementa punteros inteligentes basados en políticas.
Otras referencias sobre punteros inteligentes basados en políticas, que abordan el problema del pobre soporte de la optimización de base vacía junto con la herencia múltiple de muchos compiladores:
fuente
Además de los dados, también hay algunos orientados a la seguridad:
SaferCPlusPlus
mse::TRefCountingPointer
es un puntero inteligente de recuento de referencias comostd::shared_ptr
. La diferencia es quemse::TRefCountingPointer
es 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, usestd::shared_ptr
, de lo contrario,mse::TRefCountingPointer
es más óptimo.mse::TScopeOwnerPointer
es similar aboost::scoped_ptr
, pero funciona en conjunto conmse::TScopeFixedPointer
una relación de puntero "fuerte-débil" de tipo likestd::shared_ptr
ystd::weak_ptr
.mse::TScopeFixedPointer
apunta 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::TRegisteredPointer
se 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 quemse::TRegisteredPointer
tiene 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).
fuente