Un pointeur "brut" n'est pas géré. C'est-à-dire la ligne suivante:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... perdra de la mémoire si un accompagnement delete
n'est pas exécuté au bon moment.
auto_ptr
Afin de minimiser ces cas, a std::auto_ptr<>
été introduit. En raison des limitations de C ++ antérieures à la norme 2011, il est toutefois très facile auto_ptr
de perdre de la mémoire. Cela suffit pour des cas limités, comme celui-ci:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
L'un de ses cas d'utilisation les plus faibles est celui des conteneurs. En effet, si une copie d'un auto_ptr<>
est créée et que l'ancienne copie n'est pas soigneusement réinitialisée, le conteneur peut supprimer le pointeur et perdre des données.
unique_ptr
En remplacement, C ++ 11 a introduit std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Un tel unique_ptr<>
sera correctement nettoyé, même s'il est passé entre les fonctions. Pour ce faire, il représente sémantiquement la "propriété" du pointeur - le "propriétaire" le nettoie. Cela le rend idéal pour une utilisation dans des conteneurs:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Contrairement à auto_ptr<>
, unique_ptr<>
se comporte bien ici, et lors du vector
redimensionnement, aucun des objets ne sera supprimé accidentellement lors de la vector
copie de son magasin de sauvegarde.
shared_ptr
et weak_ptr
unique_ptr<>
est utile, bien sûr, mais il existe des cas où vous souhaitez que deux parties de votre base de code puissent faire référence au même objet et copier le pointeur tout en garantissant un nettoyage correct. Par exemple, une arborescence peut ressembler à ceci lorsque vous utilisez std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Dans ce cas, nous pouvons même conserver plusieurs copies d'un nœud racine et l'arborescence sera correctement nettoyée lorsque toutes les copies du nœud racine seront détruites.
Cela fonctionne parce que chacun shared_ptr<>
conserve non seulement le pointeur sur l'objet, mais également un nombre de références de tous les shared_ptr<>
objets qui font référence au même pointeur. Lorsqu'un nouveau est créé, le nombre augmente. Quand on est détruit, le compte diminue. Lorsque le compte atteint zéro, le pointeur est delete
d.
Cela pose donc un problème: les structures à double liaison se retrouvent avec des références circulaires. Disons que nous voulons ajouter un parent
pointeur sur notre arbre Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Maintenant, si on enlève un Node
, il y a une référence cyclique à cela. Il ne sera jamais delete
d car son compte de référence ne sera jamais égal à zéro.
Pour résoudre ce problème, vous utilisez un std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Désormais, les choses fonctionneront correctement et la suppression d'un nœud ne laissera pas de références bloquées au nœud parent. Cependant, il est un peu plus compliqué de marcher dans l’arbre:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
De cette façon, vous pouvez verrouiller une référence sur le nœud et vous avez une garantie raisonnable qu'elle ne disparaîtra pas tant que vous y travaillerez, car vous en conservez une shared_ptr<>
.
make_shared
et make_unique
Maintenant, il y a quelques problèmes mineurs avec shared_ptr<>
et unique_ptr<>
cela devrait être résolu. Les deux lignes suivantes ont un problème:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Si thrower()
lève une exception, les deux lignes perdront de la mémoire. Et plus que cela, shared_ptr<>
maintient le compte de références loin de l’objet qu’il pointe et cela peut signifier une seconde allocation). Ce n'est généralement pas souhaitable.
C ++ 11 fournit std::make_shared<>()
et C ++ 14 fournit std::make_unique<>()
pour résoudre ce problème:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Maintenant, dans les deux cas, même si thrower()
une exception est générée, il n'y aura pas de fuite de mémoire. En prime, il make_shared<>()
a la possibilité de créer son compte de référence dans le même espace mémoire que son objet géré, ce qui peut être plus rapide et économiser quelques octets de mémoire, tout en vous offrant une garantie de sécurité exceptionnelle!
Notes sur Qt
Il convient toutefois de noter que Qt, qui doit prendre en charge les compilateurs antérieurs à C ++ 11, dispose de son propre modèle de récupération de place: de nombreux utilisateurs QObject
disposent d’un mécanisme leur permettant d’être détruits correctement sans que l’utilisateur delete
n’en ait besoin .
Je ne sais pas comment QObject
va se comporter quand il sera géré par des pointeurs gérés par C ++ 11, je ne peux donc pas dire que ce shared_ptr<QDialog>
soit une bonne idée. Je n'ai pas suffisamment d'expérience avec Qt, mais je pense que Qt5 a été ajusté pour ce cas d'utilisation.