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_refes un detalle de diseño destd::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.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.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 recurredeletea 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_ptrpara permitir la copia y asignación en ciertas situaciones. Específicamente, se puede usar para convertir un valor no constantestd::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 sustituyastd::auto_ptrserá bastante similar, excepto con las mejoras clave para corregir las debilidades destd::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 anteriorstd::auto_ptr.std::shared_ptr- Creo que esto se basa en TR1 y se haboost::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 unastd::shared_ptrasignació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_ptrlo tanto, no impedirá la eliminación del objeto si elstd::shared_ptrrecuento de referencias cae a cero. Para obtener acceso al puntero sin formato, primero tendrá que accederstd::shared_ptrllamando llamando,locklo que devolverá un espacio vacíostd::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 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_ptrcuando sea utilizable. Es comparablestd::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 queshared_ptrya que le permite tener una única asignación de montón por objeto. (gracias Arvid)boost::shared_array- Esto esboost::shared_ptrpara 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_ptrcon estos. Sin embargo, también puede usar aboost::shared_ptr<std::vector<>>para una funcionalidad similar y recuperar la capacidad de usarboost::weak_ptrpara referencias.boost::scoped_array- Esto esboost::scoped_ptrpara matrices. Al igual que conboost::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 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::arraylugar.Qt
QPointer- Introducido en Qt 4.0, este es un puntero inteligente "débil" que solo funciona conQObjectclases 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_ptraunque tiene algo de seguridad de subproceso incorporado, pero requiere que incluyas métodos de conteo de referencia (refyderef) 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 aQSharedDataPointerexcepto que no llama implícitamentedetach(). 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 comostd::weak_ptryboost::weak_ptresto se usa junto conQSharedPointercuando 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_ptrdiferencia de las versiones Qt de punteros compartidos y débiles. Funciona para proporcionar un puntero inteligente de un solo propietario sin la sobrecarga deQSharedPointerlo 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_ptroboost::scoped_ptrpara las que puede usar .fuente
intrusive_ptrnormalmente es más eficiente queshared_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.shared_ptrs? (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::TRefCountingPointeres un puntero inteligente de recuento de referencias comostd::shared_ptr. La diferencia es quemse::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, usestd::shared_ptr, de lo contrario,mse::TRefCountingPointeres más óptimo.mse::TScopeOwnerPointeres similar aboost::scoped_ptr, pero funciona en conjunto conmse::TScopeFixedPointeruna relación de puntero "fuerte-débil" de tipo likestd::shared_ptrystd::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 quemse::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).
fuente