¿Qué tipo de puntero uso y cuándo?

Resuelto sbi asked hace 12 años • 4 respuestas

Ok, la última vez que escribí C++ para ganarme la vida, std::auto_ptrera todo lo que tenía disponible la biblioteca estándar y boost::shared_ptrestaba de moda. Realmente nunca investigué el aumento de otros tipos de punteros inteligentes proporcionados. Entiendo que C++ 11 ahora proporciona algunos de los tipos que se le ocurrieron, pero no todos.

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

sbi avatar Jan 03 '12 05:01 sbi
Aceptado

Propiedad compartida:
El shared_ptrestándar weak_ptradoptado es prácticamente el mismo que el de sus homólogos de Boost . Úsalos cuando necesites compartir un recurso y no sepas cuál será el último en quedar vivo. Úselo weak_ptrpara observar el recurso compartido sin influir en su vida útil, no para romper ciclos. Los ciclos shared_ptrnormalmente no deberían ocurrir: dos recursos no pueden poseerse entre sí.

Tenga en cuenta que Boost también ofrece shared_array, que podría ser una alternativa adecuada a shared_ptr<std::vector<T> const>.

A continuación, ofrece Boost intrusive_ptr, que son una solución liviana si su recurso ya ofrece administración contada por referencia y desea adoptarla según el principio RAII. Éste no fue adoptado por la norma.

Propiedad única:
Boost también tiene un scoped_ptr, que no se puede copiar y para el cual no se puede especificar un eliminador. std::unique_ptrestá boost::scoped_ptrtomando esteroides y debería ser su opción predeterminada cuando necesite un puntero inteligente . Le permite especificar un eliminador en los argumentos de su plantilla y es móvil , a diferencia de boost::scoped_ptr. También es totalmente utilizable en contenedores STL siempre que no utilice operaciones que necesiten tipos copiables (obviamente).

Tenga en cuenta nuevamente que Boost tiene una versión de matriz: scoped_array, que el estándar unificó al requerir std::unique_ptr<T[]>una especialización parcial que controlará delete[]el puntero en lugar de deletehacerlo (con la default_deleter). std::unique_ptr<T[]>también ofrece operator[]en lugar de operator*y operator->.

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

La plantilla de clase auto_ptrestá en desuso. [ Nota: la plantilla de clase unique_ptr(20.7.1) proporciona una mejor solución. —nota final ]

Sin propiedad:
utilice punteros tontos (punteros sin formato) o referencias para referencias que no son de propiedad a recursos y cuando sepa que el recurso sobrevivirá al objeto/alcance de referencia. Prefiera referencias y utilice punteros sin formato cuando necesite capacidad de nulidad o restablecimiento.

Si desea una referencia no propietaria a un recurso, pero no sabe si el recurso sobrevivirá al objeto que hace referencia a él, empaquete el recurso en a shared_ptry use a weak_ptr; puede probar si el padre shared_ptrestá vivo con lock, lo que hará devuelve un shared_ptrvalor no nulo si el recurso aún existe. Si desea probar si el recurso está muerto, utilice expired. Los dos pueden parecer similares, pero son muy diferentes cuando se trata de ejecución concurrente, ya que expiredsolo garantiza su valor de retorno para esa única declaración. Una prueba aparentemente inocente como

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

es una posible condición de carrera.

Xeo avatar Jan 02 '2012 23:01 Xeo

Decidir qué puntero inteligente utilizar es una cuestión de propiedad . Cuando se trata de gestión de recursos, el objeto A es propietario del objeto B si tiene el control de la vida útil del objeto B. Por ejemplo, las variables miembro son propiedad de sus respectivos objetos porque la vida útil de las variables miembro está ligada a la vida útil del objeto. Usted elige punteros inteligentes según la propiedad del objeto.

Tenga en cuenta que la propiedad en un sistema de software está separada de la propiedad, ya que la consideraríamos fuera del software. Por ejemplo, una persona puede ser "dueña" de su casa, pero eso no significa necesariamente que un Personobjeto tenga control sobre la vida útil de un Houseobjeto. Combinar estos conceptos del mundo real con conceptos de software es una forma segura de programarse en un agujero.


Si tienes la propiedad exclusiva del objeto, utiliza std::unique_ptr<T>.

Si tiene propiedad compartida del objeto...
- Si no hay ciclos de propiedad, use std::shared_ptr<T>.
- Si hay ciclos, definir una "dirección" y utilizar std::shared_ptr<T>en un sentido y std::weak_ptr<T>en el otro.

Si el objeto es de su propiedad, pero existe la posibilidad de que no tenga propietario, utilice punteros normales T*(por ejemplo, punteros principales).

Si el objeto es de su propiedad (o tiene existencia garantizada), utilice referencias T&.


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

Los costos:

  • Si tiene un eliminador personalizado (por ejemplo, utiliza grupos de asignación), esto generará una sobrecarga por puntero que puede evitarse fácilmente mediante la eliminación manual.
  • std::shared_ptrtiene la sobrecarga de un incremento del recuento de referencias en la copia, más una disminución en la destrucción seguida de una verificación de recuento 0 con eliminación del objeto retenido. Dependiendo de la implementación, esto puede inflar su código y causar problemas de rendimiento.
  • Tiempo de compilación. Como ocurre con todas las plantillas, los punteros inteligentes contribuyen negativamente a los tiempos de compilación.

Ejemplos:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> 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 nullptrraíz), por lo que utiliza un puntero normal. Un árbol binario (con semántica de valores) tiene propiedad exclusiva de sus hijos, por lo que esos son std::unique_ptr.

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

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

Peter Alexander avatar Jan 02 '2012 23:01 Peter Alexander

Úselo unique_ptr<T>todo el tiempo excepto cuando necesite un recuento de referencias, en cuyo caso úselo shared_ptr<T>(y en casos muy raros, weak_ptr<T>para evitar ciclos de referencia). En casi todos los casos, la propiedad única transferible está bien.

Punteros sin procesar: buenos solo si necesita rendimientos covariantes, lo cual puede suceder. De lo contrario, no son tremendamente útiles.

Punteros de matriz: unique_ptrtiene una especialización para T[]la cual invoca automáticamente delete[]el resultado, por lo que puedes hacerlo de forma segura, unique_ptr<int[]> p(new int[42]);por ejemplo. shared_ptraún necesitaría un eliminador personalizado, pero no necesitaría un puntero de matriz único o compartido especializado. Por supuesto, estas cosas suelen ser mejor reemplazadas por de std::vectortodos modos. Desafortunadamente, shared_ptrno proporciona una función de acceso a la matriz, por lo que aún tendría que llamar manualmente get(), pero unique_ptr<T[]>proporciona operator[]en lugar de operator*y operator->. En cualquier caso, debes comprobarlo tú mismo. Esto lo hace shared_ptrun poco menos fácil de usar, aunque podría decirse que la ventaja genérica y la ausencia de dependencia de Boost los convierte unique_ptren shared_ptrlos ganadores nuevamente.

Punteros con alcance: se vuelven irrelevantes por unique_ptr, al igual que auto_ptr.

Realmente no hay nada más. En C++03 sin semántica de movimientos 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_ptro interprocess_ptr. Sin embargo, son muy específicos y completamente innecesarios en el caso general.

Puppy avatar Jan 02 '2012 23:01 Puppy

Casos de cuándo utilizar unique_ptr:

  • Métodos de fábrica
  • Miembros que son punteros (incluido pimpl)
  • Almacenamiento de punteros en contenedores stl (para evitar movimientos)
  • Uso de grandes objetos dinámicos locales.

Casos de cuándo utilizar shared_ptr:

  • Compartir objetos entre hilos
  • Compartir objetos en general

Casos de cuándo utilizar weak_ptr:

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

Siéntete libre de editar y agregar más

 avatar Jan 02 '2012 23:01