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
.
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 claseunique_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:
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. 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
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:
Casos de cuándo usar shared_ptr:
Casos de cuándo usar weak_ptr:
Siéntase libre de editar y agregar más