¿Qué tipo de puntero uso cuando?

Ok, entonces la última vez que escribí C ++ for a living, std::auto_ptr era todo lo que std lib tenía disponible, y boost::shared_ptr estaba de moda. Nunca me fijé realmente en los otros tipos de impulso de puntero inteligente proporcionados. Entiendo que C ++ 11 ahora proporciona algunos de los tipos propuestos, pero no todos.

Entonces, ¿alguien tiene un algoritmo simple para determinar cuándo usar qué puntero inteligente? Preferiblemente, incluye consejos sobre punteros tontos (punteros sin formato como T* ) y el rest de los indicadores inteligentes de impulso. (Algo así sería genial).

Propiedad compartida:
El shared_ptr y el weak_ptr el estándar adoptó son más o menos los mismos que sus homólogos de Boost . Úselos cuando necesite compartir un recurso y no sepa cuál será el último en estar vivo. Utilice weak_ptr para observar el recurso compartido sin influir en su duración, no para romper ciclos. Los ciclos con shared_ptr normalmente no deberían suceder: dos recursos no pueden shared_ptr entre sí.

Tenga en cuenta que Boost ofrece adicionalmente shared_array , que podría ser una alternativa adecuada para shared_ptr const> .

A continuación, Boost ofrece intrusive_ptr , que es una solución liviana si su recurso ya ofrece administración contada de referencia y desea adoptarla según el principio de RAII. Este no fue adoptado por el estándar.

Propiedad única:
Boost también tiene un scoped_ptr , que no se puede copiar y para el que no se puede especificar un eliminador. std::unique_ptr es boost::scoped_ptr con esteroides y debe ser su opción predeterminada cuando necesite un puntero inteligente . Le permite especificar un eliminador en sus argumentos de plantilla y es movible , a diferencia de boost::scoped_ptr . También es completamente utilizable en contenedores STL siempre que no utilice operaciones que necesiten tipos copiables (obviamente).

Nótese de nuevo, que Boost tiene una versión de matriz: scoped_array , que el estándar unificó al requerir std::unique_ptr especialización parcial que delete[] el puntero en lugar de delete (con la default_delete r). std::unique_ptr también ofrece operator[] lugar de operator* y operator-> .

Tenga en cuenta que std::auto_ptr aún está en el estándar, pero está en desuso . §D.10 [depr.auto.ptr]

La plantilla de clase auto_ptr está en desuso. [ Nota: la plantilla de clase unique_ptr (20.7.1) proporciona una mejor solución. -Finalizar nota ]

Sin propiedad:
Utilice punteros estúpidos (punteros sin formato) o referencias para referencias no propietarias a los recursos y cuando sepa que el recurso sobrevivirá al objeto / ámbito de referencia. Prefiere las referencias y utiliza punteros sin formato cuando necesites anular o reiniciar.

Si desea una referencia no propietaria de un recurso, pero no sabe si el recurso sobrevivirá al objeto que lo hace referencia, empaquete el recurso en un shared_ptr y use un weak_ptr ; puede probar si el padre shared_ptr está activo con lock , que devolverá un shared_ptr que no es nulo si el recurso aún existe. Si desea probar si el recurso está muerto, el uso expired . Los dos pueden parecer similares, pero son muy diferentes frente a la ejecución simultánea, ya que expired solo garantiza su valor de retorno para esa statement única. Una prueba aparentemente inocente como

 if(!wptr.expired()) something_assuming_the_resource_is_still_alive(); 

es una condición de carrera potencial.

Decidir qué puntero inteligente usar es una cuestión de propiedad . Cuando se trata de la gestión de recursos, el objeto A posee el objeto B si controla el tiempo de vida del objeto B. Por ejemplo, las variables miembro son propiedad de sus respectivos objetos porque la duración de las variables miembro está vinculada a la duración del objeto. Usted elige punteros inteligentes en función de cómo se posee el objeto.

Tenga en cuenta que la propiedad en un sistema de software es independiente de la propiedad, ya que pensaríamos en ella fuera del software. Por ejemplo, una persona puede “poseer” su casa, pero eso no significa necesariamente que un objeto Person tenga control sobre la vida útil de un objeto de la House . Confundir estos conceptos del mundo real con conceptos de software es una forma segura de progtwigrte en un agujero.


Si tiene la propiedad exclusiva del objeto, use std::unique_ptr .

Si has compartido la propiedad del objeto …
– Si no hay ciclos en la propiedad, use std::shared_ptr .
– Si hay ciclos, defina una “dirección” y use std::shared_ptr en una dirección y std::weak_ptr en la otra.

Si el objeto lo posee, pero existe la posibilidad de no tener un propietario, utilice los punteros normales T* (por ejemplo, los punteros principales).

Si el objeto lo posee (o si tiene existencia garantizada), use las referencias T& .


Advertencia: tenga en cuenta los costos de los punteros inteligentes. En entornos con memoria o rendimiento limitado, podría ser beneficioso utilizar punteros normales con un esquema más manual para administrar la memoria.

Los costos:

  • Si tiene un eliminador personalizado (por ejemplo, utiliza grupos de asignación), esto implicará una sobrecarga por puntero que se puede evitar fácilmente mediante la eliminación manual.
  • std::shared_ptr tiene la sobrecarga de un incremento de recuento de referencia en la copia, más una disminución en la destrucción seguida de un conteo de 0 con la eliminación del objeto retenido. Dependiendo de la implementación, esto puede inflar su código y causar problemas de rendimiento.
  • Tiempo de comstackción. Al igual que con todas las plantillas, los indicadores inteligentes contribuyen negativamente a los tiempos de comstackción.

Ejemplos:

 struct BinaryTree { Tree* m_parent; std::unique_ptr m_children[2]; // or use std::array... }; 

Un árbol binario no posee su padre, pero la existencia de un árbol implica la existencia de su padre (o nullptr para raíz), por lo que utiliza un puntero normal. Un árbol binario (con semántica de valores) tiene la propiedad exclusiva de sus hijos, por lo que estos son std::unique_ptr .

 struct ListNode { std::shared_ptr m_next; std::weak_ptr m_prev; }; 

Aquí, el nodo de lista posee sus listas anterior y siguiente, por lo que definimos una dirección y usamos shared_ptr para next y weak_ptr para prev para romper el ciclo.

Utilice unique_ptr todo el tiempo, excepto cuando necesite conteo de referencias, en cuyo caso use shared_ptr (y para casos muy raros, weak_ptr para evitar ciclos de referencia). En casi todos los casos, la propiedad única transferible está bien.

Punteros crudos: Bueno solo si necesita retornos covariantes, apuntando sin propiedad que puede suceder. No son terriblemente útiles de lo contrario.

Punteros de matriz: unique_ptr tiene una especialización para T[] que llama automáticamente a delete[] en el resultado, para que pueda hacer de forma segura unique_ptr p(new int[42]); por ejemplo. shared_ptr aún necesitaría un eliminador personalizado, pero no necesitaría un puntero de matriz único o compartido especializado. Por supuesto, tales cosas generalmente son mejor reemplazadas por std::vector todos modos. Lamentablemente, shared_ptr no proporciona una función de acceso a la matriz, por lo que aún tendrá que llamar manualmente a get() , pero unique_ptr proporciona operator[] lugar de operator* y operator-> . En cualquier caso, tienes que limitarte a ti mismo. Esto hace que shared_ptr sea ​​menos fácil de usar, aunque podría decirse que la ventaja genérica y la ausencia de dependencia de Boost hace que unique_ptr y shared_ptr los ganadores nuevamente.

Punteros de scope: irrelevante por unique_ptr , al igual que auto_ptr .

Realmente no hay nada más. En C ++ 03 sin semántica de movimiento esta situación era muy complicada, pero en C ++ 11 el consejo es muy simple.

Todavía hay usos para otros punteros inteligentes, como intrusive_ptr o interprocess_ptr . Sin embargo, son muy específicos y completamente innecesarios en el caso general.

Casos de cuándo usar unique_ptr:

  • Métodos de fábrica
  • Miembros que son punteros (chulos incluidos)
  • Almacenamiento de punteros en stl containters (para evitar movimientos)
  • Uso de grandes objetos dynamics locales

Casos de cuándo usar shared_ptr:

  • Compartir objetos a través de hilos
  • Enlazar o capturar punteros (uso de lambda o std :: bind)
  • Compartir objetos en general
  • Borradores personalizados

Casos de cuándo usar weak_ptr:

  • Mapa grande que actúa como una referencia general (por ejemplo, un mapa de todos los sockets abiertos)

Siéntase libre de editar y agregar más