Décider quel pointeur intelligent utiliser est une question de propriété . En ce qui concerne la gestion des ressources, l'objet A possède l' objet B s'il contrôle la durée de vie de l'objet B. Par exemple, les variables membres appartiennent à leurs objets respectifs car la durée de vie des variables membres est liée à la durée de vie de l'objet. Vous choisissez des pointeurs intelligents en fonction de la propriété de l'objet.
Notez que la propriété d'un système logiciel est distincte de la propriété comme nous le penserions en dehors du logiciel. Par exemple, une personne peut «posséder» sa maison, mais cela ne signifie pas nécessairement qu'un Person
objet a le contrôle sur la durée de vie d'un House
objet. La confrontation de ces concepts du monde réel avec des concepts logiciels est une façon infaillible de vous programmer dans un trou.
Si vous êtes l'unique propriétaire de l'objet, utilisez std::unique_ptr<T>
.
Si vous avez partagé la propriété de l'objet ...
- S'il n'y a pas de cycle de propriété, utilisez std::shared_ptr<T>
.
- S'il y a des cycles, définir une "direction" et utiliser std::shared_ptr<T>
dans un sens et std::weak_ptr<T>
dans l'autre.
Si l'objet vous appartient, mais qu'il est possible de ne pas avoir de propriétaire, utilisez des pointeurs normaux T*
(par exemple, des pointeurs parents).
Si l'objet vous appartient (ou a une existence garantie d'une autre manière), utilisez des références T&
.
Avertissement: soyez conscient des coûts des pointeurs intelligents. Dans les environnements à mémoire ou performances limitées, il peut être avantageux d'utiliser simplement des pointeurs normaux avec un schéma plus manuel pour gérer la mémoire.
Les coûts:
- Si vous avez un suppresseur personnalisé (par exemple, vous utilisez des pools d'allocation), cela entraînera des frais généraux par pointeur qui peuvent être facilement évités par une suppression manuelle.
std::shared_ptr
a la surcharge d'un incrément de comptage de référence lors de la copie, plus un décrément lors de la destruction suivi d'une vérification de 0 comptage avec suppression de l'objet retenu. Selon l'implémentation, cela peut alourdir votre code et entraîner des problèmes de performances.
- Compiler le temps. Comme avec tous les modèles, les pointeurs intelligents contribuent négativement aux temps de compilation.
Exemples:
struct BinaryTree
{
Tree* m_parent;
std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};
Un arbre binaire ne possède pas son parent, mais l'existence d'un arbre implique l'existence de son parent (ou nullptr
pour root), de sorte qu'il utilise un pointeur normal. Un arbre binaire (avec une sémantique de valeur) a la propriété exclusive de ses enfants, donc ce sont std::unique_ptr
.
struct ListNode
{
std::shared_ptr<ListNode> m_next;
std::weak_ptr<ListNode> m_prev;
};
Ici, le nœud de liste possède ses listes suivante et précédente, nous définissons donc une direction et utilisons shared_ptr
pour suivant et weak_ptr
pour prev pour rompre le cycle.