smart punteros pointer inteligentes c++ smart-pointers c++-faq

punteros - ¿Qué implementaciones de C++ Smart Pointer están disponibles?



smart pointer c++ (3)

Comparaciones, pros, contras y cuándo usarlos

Este es un spin-off de un hilo de recolección de basura donde lo que pensé que era una respuesta simple generaba muchos comentarios sobre algunas implementaciones específicas de punteros inteligentes, 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? Solo pros y contras simples o excepciones y trampas a algo que de otro modo podría pensar que debería funcionar.

Publiqué algunas implementaciones que utilicé o al menos pasé por alto y consideré utilizar como respuesta a continuación y mi comprensión de sus diferencias y similitudes que pueden no ser 100% precisas, así que no duden en consultarme o corregirme cuando sea necesario.

El objetivo es aprender sobre algunos objetos y bibliotecas nuevos o corregir mi uso y comprensión de las implementaciones existentes que ya están en uso y terminar con una referencia decente para los demás.


C ++ 03

std::auto_ptr - Tal vez uno de los originales que sufrió del primer síndrome de reclutamiento solo brindaba instalaciones limitadas de recolección de basura. El primer inconveniente es que llama a delete después de la destrucción, lo que los hace inaceptables para mantener los objetos asignados por la matriz ( new[] ). Asume la propiedad del puntero para que dos punteros automáticos no contengan el mismo objeto. La asignación transferirá la propiedad y restablecerá el puntero automático rvalue a un puntero nulo. Lo que conduce quizás al peor inconveniente; no se pueden usar dentro de contenedores STL debido a la imposibilidad antes mencionada de ser copiado. El golpe final a 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 junto con std::auto_ptr para permitir la copia y asignación en ciertas situaciones. Específicamente se puede usar para convertir un std::auto_ptr no const a un lvalue usando el truco Colvin-Gibbons también conocido como un constructor de movimiento para transferir la propiedad.

Por el contrario, quizás std::auto_ptr realidad no estaba destinado a ser utilizado como un puntero inteligente de propósito general para la recolección automática de basura. La mayoría 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 reemplazará a std::auto_ptr , será bastante similar, excepto con las mejoras clave para corregir las debilidades de std::auto_ptr como trabajar con matrices, protección lvalue a través de un constructor de copia privada, que sea utilizable con contenedores STL y algoritmos, etc. Debido a que su sobrecarga de rendimiento y la huella de memoria son limitadas, este es un candidato ideal para reemplazar, o tal vez describir como apropiadamente, punteros crudos. Como lo "único" implica que solo hay un propietario del puntero al igual que el std::auto_ptr anterior std::auto_ptr .

std::shared_ptr - Creo que esto se basa en TR1 y boost::shared_ptr pero mejorado para incluir aliasing y aritmética de puntero también. En resumen, envuelve un puntero inteligente contado de referencia alrededor de un objeto dinámicamente asignado. Como "compartido" implica que el puntero puede ser propiedad de más de un puntero compartido cuando la última referencia del último puntero compartido sale del alcance, el objeto se eliminará de forma adecuada. Estos también son seguros para subprocesos y pueden manejar tipos incompletos en la mayoría de los casos. std::make_shared se puede usar para construir eficientemente std::shared_ptr con una asignación de montón usando el asignador predeterminado.

std::weak_ptr - También basado en TR1 y boost::weak_ptr . Esta es una referencia a un objeto propiedad de std::shared_ptr y, por lo tanto, no impedirá la eliminación del objeto si el std::shared_ptr referencia std::shared_ptr cae a cero. Para obtener acceso al puntero sin std::shared_ptr , primero deberá acceder a std::shared_ptr llamando al lock que devolverá un std::shared_ptr vacío si el puntero de propiedad ha caducado y ya se ha destruido. Esto es principalmente útil para evitar recuentos indefinidos de referencias pendientes 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.) es un puntero inteligente contado de referencia compartida. 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 me vienen a la mente inconvenientes sobre la norma con respecto a los indicadores 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 de un boost::shared_ptr . No sorprende que llame a lock() para acceder al puntero compartido "fuerte" y debe verificar para asegurarse de que sea válido, ya que podría haberse destruido. Solo asegúrate de no guardar el puntero compartido devuelto y déjalo fuera del alcance tan pronto como hayas terminado, de lo contrario estarás de vuelta en el problema de referencia cíclica donde se colgarán tus recuentos de referencia 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 alternativa de mejor rendimiento para boost::shared_ptr cuando se pueda usar. Es comparable a std::auto_ptr especialmente en el 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 desde mi punto de vista está diseñado para usarse al crear tus propias clases compatibles con puntero inteligente. Necesita 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 hilos. En el lado positivo, esto probablemente le brinde la forma más personalizada de escoger y elegir exactamente cuánto o qué tan poco "inteligente" desea. intrusive_ptr suele ser más eficiente que shared_ptr ya que le permite tener una única asignación de montón por objeto. (gracias Arvid)

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

boost::scoped_array - Esto es un boost::scoped_ptr para matrices. Al igual que con boost::shared_array todos los elementos esenciales necesarios se boost::shared_array . Éste no se puede copiar y, por lo tanto, no se puede usar en contenedores STL. He encontrado que casi en cualquier lugar donde quiera usar esto probablemente solo pueda usar std::vector . Nunca he determinado cuál es realmente más rápido o tiene menos sobrecarga, pero este conjunto de ámbito parece mucho menos complicado que un vector STL. Cuando desee mantener la asignación en la pila, considere boost::array lugar.

Qt

QPointer : introducido en Qt 4.0, este es un puntero inteligente "débil" que solo funciona con QObject y clases derivadas, que en el framework Qt es casi todo, así que eso no es realmente una limitación. Sin embargo, existen limitaciones, a saber, que no proporciona un puntero "fuerte" y, aunque puede verificar si el objeto subyacente es válido con isNull() , podría encontrar el objeto destruido inmediatamente después de pasar esa comprobación, especialmente en entornos de subprocesos múltiples . Qt personas consideran que esta obsoleta, creo.

QSharedDataPointer : este es un puntero inteligente "fuerte" potencialmente comparable con boost::intrusive_ptr aunque tiene cierta seguridad incorporada en el subproceso pero requiere que incluya métodos de recuento de referencias ( ref y deref ) que puede hacer subclasificando QSharedData . Al igual que con gran parte de Qt, los objetos se utilizan mejor a través de una amplia herencia y la creación de subclases parece ser el diseño previsto.

QExplicitlySharedDataPointer : muy similar a QSharedDataPointer excepto que no llama implícitamente a QSharedDataPointer detach() . QSharedDataPointer esta versión 2.0 de QSharedDataPointer como que el ligero aumento en el control sobre exactamente cuándo desprenderse después de que el recuento de referencias caiga en cero no es particularmente digno de un objeto completamente nuevo.

QSharedPointer : recuento de referencias atómicas, hilo seguro, puntero compartible, eliminaciones personalizadas (soporte de matriz), suena como todo lo que debe ser un puntero inteligente. Esto es lo que utilizo principalmente como un puntero inteligente en Qt y me parece comparable con boost:shared_ptr aunque probablemente significativamente más sobrecarga como muchos objetos en Qt.

QWeakPointer - ¿ QWeakPointer un patrón recurrente? Al igual que std::weak_ptr y boost::weak_ptr esto se usa junto con QSharedPointer cuando necesita referencias entre dos punteros inteligentes que de lo contrario harían que sus objetos nunca se eliminen.

QScopedPointer : este nombre también debería ser familiar y, de hecho, estaba basado en boost::scoped_ptr diferencia de las versiones Qt de punteros compartidos y débiles. Funciona para proporcionar un puntero inteligente de propietario único sin la sobrecarga de QSharedPointer que lo hace más adecuado para compatibilidad, código de excepción de seguridad y todo lo que puede usar std::auto_ptr o boost::scoped_ptr para.


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

SaferCPlusPlus

mse::TRefCountingPointer es una referencia que cuenta puntero inteligente como std::shared_ptr . La diferencia es que mse::TRefCountingPointer es más seguro, más pequeño y más rápido, pero no tiene ningún mecanismo de seguridad de subprocesos. Y se presenta en versiones "no nulas" y "fijas" (no reorientables) que se puede suponer de manera segura que siempre apuntan a un objeto asignado de forma válida. Entonces, básicamente, si su objeto de destino se comparte entre subprocesos asincrónicos, entonces use std::shared_ptr ; de lo contrario, mse::TRefCountingPointer es más óptimo.

mse::TScopeOwnerPointer es similar a boost::scoped_ptr , pero funciona junto con mse::TScopeFixedPointer en una relación de puntero "strong-weak" como std::shared_ptr y std::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 no necesitar mecanismos de seguridad (en tiempo de ejecución).

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

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