c++ - que - punteros en c ejemplos
¿Qué tipo de puntero utilizo cuando? (4)
Ok, así que la última vez que escribí C ++ para boost::shared_ptr
vida, std::auto_ptr
fue todo lo que había disponible en la boost::shared_ptr
, y boost::shared_ptr
estaba de moda. Realmente nunca busqué en los otros tipos de punteros inteligentes que se proporcionan. Entiendo que C ++ 11 ahora proporciona algunos de los tipos de impulso creados, pero no todos.
Entonces, ¿alguien tiene un algoritmo simple para determinar cuándo usar qué puntero inteligente? Preferiblemente, se incluyen consejos sobre punteros tontos (punteros en bruto como T*
) y el resto de los punteros inteligentes de refuerzo. (Algo así sería genial).
Casos de cuándo usar unique_ptr
:
- Métodos de fábrica
- Miembros que son punteros (pimpl incluido)
- Almacenamiento de punteros en contenedores stl (para evitar movimientos)
- Uso de grandes objetos dinámicos locales.
Casos de cuándo usar shared_ptr
:
- Compartiendo objetos a través de hilos
- Compartiendo objetos en general
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
Decidir qué puntero inteligente usar es una cuestión de propiedad . Cuando se trata de la administración de recursos, el objeto A posee el objeto B si controla 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á vinculada a la vida útil del objeto. Eliges 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 podríamos pensar fuera de él. Por ejemplo, una persona puede "poseer" su hogar, pero eso no significa necesariamente que un objeto Person
tenga control sobre la vida útil de un objeto House
. Combinar estos conceptos del mundo real con conceptos de software es una forma segura de programarse en un agujero.
Si tiene la propiedad exclusiva del objeto, use std::unique_ptr<T>
.
Si ha compartido la propiedad del objeto ...
- Si no hay ciclos en la propiedad, use std::shared_ptr<T>
.
- Si hay ciclos, define una "dirección" y usa std::shared_ptr<T>
en una dirección y std::weak_ptr<T>
en la otra.
Si el objeto es suyo, pero existe la posibilidad de que no tenga propietario, utilice los punteros normales T*
(por ejemplo, los punteros principales).
Si el objeto es de su propiedad (o si tiene una existencia garantizada), utilice las referencias T&
.
Advertencia: tenga en cuenta los costos de los punteros inteligentes. En entornos de memoria o de 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 supondrá una sobrecarga por puntero que puede evitarse fácilmente con la eliminación manual.
-
std::shared_ptr
tiene la sobrecarga de un incremento en el recuento de referencia en la copia, más un decremento en la destrucción seguido de una verificación de 0 recuentos 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 compilación. Al igual que 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 nullptr
para la raíz), por lo que utiliza un puntero normal. Un árbol binario (con semántica de valor) tiene propiedad exclusiva de sus hijos, por lo que 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_ptr
para next y weak_ptr
para prev para romper el ciclo.
Use unique_ptr<T>
todo el tiempo, excepto cuando necesite un conteo de referencias, en cuyo caso use shared_ptr<T>
(y, en casos muy raros, weak_ptr<T>
para evitar los ciclos de referencia). En casi todos los casos, la propiedad única transferible está bien.
Punteros sin procesar: bueno solo si necesita devoluciones covariantes, apuntando sin propiedad lo que puede suceder. De lo contrario no son terriblemente útiles.
Punteros de la matriz: unique_ptr
tiene una especialización para T[]
que automáticamente llama a delete[]
en el resultado, por lo que puede hacer unique_ptr<int[]> p(new int[42]);
por ejemplo. shared_ptr
aún necesitaría un eliminador personalizado, pero no necesitaría un puntero de matriz exclusivo o compartido especializado. Por supuesto, tales cosas generalmente son mejor reemplazadas por std::vector
todos modos. Desafortunadamente, shared_ptr
no proporciona una función de acceso a la matriz, por lo que aún tendría que llamar manualmente a get()
, pero unique_ptr<T[]>
proporciona operator[]
lugar de operator*
y operator->
. En cualquier caso, tienes que comprobar límites. Esto hace que shared_ptr
sea shared_ptr
poco menos fácil de usar, aunque podría decirse que la ventaja genérica y no la dependencia de Boost hace que unique_ptr
y shared_ptr
los ganadores.
Indicadores de ámbito: hechos irrelevantes por unique_ptr
, al igual que auto_ptr
.
Realmente no hay nada más que eso. En C ++ 03 sin movimiento semántico 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 nicho y completamente innecesarios en el caso general.
Propiedad compartida:
El shared_ptr
y weak_ptr
el estándar adoptado son más o menos los mismos que sus contrapartes Boost . Utilícelos 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 vida útil, no para interrumpir los ciclos. Los ciclos con shared_ptr
normalmente no deberían ocurrir: dos recursos no pueden ser propietarios 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, Boost ofrece intrusive_ptr
, que son una solución liviana si su recurso ya cuenta con una administración con conteo de referencias y desea adoptarla según el principio RAII. Este no fue adoptado por la norma.
Propiedad única:
Boost también tiene un scoped_ptr
, que no se puede copiar y para el que no puede especificar un borrado. std::unique_ptr
es boost::scoped_ptr
en los esteroides y debe ser su opción predeterminada cuando necesite un puntero inteligente . Le permite especificar un borrado en sus argumentos de 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 de nuevo que Boost tiene una versión de matriz: scoped_array
, que se unificó al estándar al requerir la especialización parcial std::unique_ptr<T[]>
que delete[]
el puntero en lugar de delete
(con el default_delete
r). std::unique_ptr<T[]>
también ofrece el operator[]
lugar del operator*
y el operator->
.
Tenga en cuenta que std::auto_ptr
todavía 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. "Nota final "
Sin propiedad:
Use punteros tontos (punteros en bruto) o referencias para referencias no propietarias a recursos y cuando sepa que el recurso sobrevivirá al objeto / ámbito de referencia. Prefiere referencias y utiliza punteros en bruto cuando necesites nulabilidad o reasentabilidad.
Si desea una referencia no propietaria a un recurso, pero no sabe si el recurso sobrevivirá al objeto que lo hace referencia, empaque el recurso en un shared_ptr
y use un weak_ptr
. Puede probar si el principal shared_ptr
está vivo. lock
, que devolverá un shared_ptr
que no es nulo si el recurso aún existe. Si desea probar si el recurso está muerto, use expired
. Los dos pueden sonar similares, pero son muy diferentes frente a la ejecución concurrente, ya que expired
solo 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 condición potencial de carrera.