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 está en 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á vinculada a la vida útil del objeto. 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 podríamos pensar fuera de él. Por ejemplo, una persona puede "ser propietaria" de su hogar, pero eso no significa necesariamente que un Person
objeto tenga control sobre la vida útil de un House
objeto. Combinar estos conceptos del mundo real con los conceptos de software es una forma segura de programarse en un hoyo.
Si tiene la propiedad exclusiva del objeto, úselo std::unique_ptr<T>
.
Si ha compartido la propiedad del objeto ...
- Si no hay ciclos de propiedad, use std::shared_ptr<T>
.
- Si hay ciclos, defina una "dirección" y úsela std::shared_ptr<T>
en una dirección y std::weak_ptr<T>
en la otra.
Si el objeto lo posee, pero existe la posibilidad de no tener un propietario, utilice punteros normales T*
(por ejemplo, punteros principales).
Si el objeto es tuyo (o de lo contrario tiene existencia garantizada), usa referencias T&
.
Advertencia: Tenga en cuenta los costos de los punteros inteligentes. En entornos con memoria o rendimiento limitado, podría ser beneficioso usar punteros normales con un esquema más manual para administrar la memoria.
Los costos:
- Si tiene un eliminador personalizado (p. Ej., Utiliza grupos de asignación), esto generará una sobrecarga por puntero que puede evitarse 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 una verificación de recuento 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 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
de raíz), por lo que utiliza un puntero normal. Un árbol binario (con semántica de valor) tiene la 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 la usamos shared_ptr
para siguiente y weak_ptr
anterior para romper el ciclo.