web-dev-qa-db-fra.com

Des pointeurs, des pointeurs intelligents ou des pointeurs partagés?

Je programme avec des pointeurs normaux, mais j'ai entendu parler de bibliothèques telles que Boost qui implémentent des pointeurs intelligents. J'ai également constaté que, dans le moteur de rendu Ogre3D, les pointeurs partagés étaient très utilisés.

Quelle est exactement la différence entre les trois, et devrais-je continuer à en utiliser juste un type?

113
tunnuz

Sydius a assez bien décrit les types:

  • Les pointeurs normaux ne sont que cela - ils indiquent quelque chose en mémoire quelque part. À qui appartient-il? Seuls les commentaires vous permettront de savoir. Qui le libère? Espérons que le propriétaire à un moment donné.
  • Les pointeurs intelligents sont un terme générique qui couvre de nombreux types. Je suppose que vous vouliez parler d'un pointeur délimité qui utilise le modèle RAII . C'est un objet alloué à la pile qui enveloppe un pointeur; lorsqu'il sort de la portée, il appelle delete sur le pointeur qu'il enveloppe. Il "possède" le pointeur contenu en ce sens qu'il est chargé de le supprimer à un moment donné. Ils vous permettent d’obtenir une référence brute au pointeur qu’ils enveloppent pour le passage à d’autres méthodes, ainsi que relâcher le pointeur, ce qui permet à une autre personne de le posséder. Les copier n'a pas de sens.
  • Pointeurs partagés est un objet alloué par pile qui englobe un pointeur afin que vous n'ayez pas à savoir à qui il appartient. Lorsque le dernier pointeur partagé d'un objet en mémoire est détruit, le pointeur enveloppé est également supprimé.

Que diriez-vous quand vous devriez les utiliser? Vous ferez un usage intensif des pointeurs étendus ou partagés. Combien de threads sont en cours d'exécution dans votre application? Si la réponse est "potentiellement beaucoup", les pointeurs partagés peuvent devenir un goulot d'étranglement s'ils sont utilisés partout. La raison en est que créer/copier/détruire un pointeur partagé doit être une opération atomique, ce qui peut nuire aux performances si plusieurs threads sont en cours d'exécution. Cependant, ce ne sera pas toujours le cas - seuls les tests vous le diront à coup sûr.

Il existe un argument (que je préfère) contre les pointeurs partagés: en les utilisant, vous autorisez les programmeurs à ignorer qui est le propriétaire d'un pointeur. Cela peut conduire à des situations délicates avec des références circulaires (Java les détectera, mais pas les pointeurs partagés) ou à la paresse générale des programmeurs dans une base de code volumineuse.

Il existe deux raisons d'utiliser des pointeurs étendus. La première concerne les opérations de sécurité et de nettoyage des exceptions simples - si vous voulez garantir qu'un objet est nettoyé malgré les exceptions, et que vous ne voulez pas empiler l'allocation, empilez-le, placez-le dans un pointeur délimité. Si l'opération réussit, vous pouvez la transférer librement vers un pointeur partagé, mais entre-temps, enregistrez le temps système avec un pointeur étendu.

L'autre cas est quand vous voulez clairement la propriété d'objet. Certaines équipes préfèrent cela, d'autres non. Par exemple, une structure de données peut renvoyer des pointeurs sur des objets internes. Sous un pointeur défini, il renverrait un pointeur brut ou une référence qui devrait être traité comme une référence faible. Il est erroné d'accéder à ce pointeur après la destruction de la structure de données qui le possède et une erreur de le supprimer. Sous un pointeur partagé, l’objet propriétaire ne peut pas détruire les données internes qu’il a renvoyées si une personne détient toujours un descripteur dessus, ce qui peut laisser des ressources ouvertes beaucoup plus longtemps que nécessaire ou bien pire, en fonction du code.

143
hazzen

le terme "pointeur intelligent" inclut pointeurs partagés, pointeurs automatiques, pointeurs de verrouillage et autres. vous vouliez dire pointeur automatique (plus ambiguement appelé "pointeur propriétaire"), pas un pointeur intelligent.

Les pointeurs Dumb (T *) ne sont jamais la meilleure solution. Ils vous obligent à gérer explicitement la mémoire, qui est prolixe, sujette aux erreurs et parfois presque impossible. Mais plus important encore, ils ne signalent pas votre intention.

Les pointeurs automatiques suppriment les pointes lors de la destruction. Pour les tableaux, préférez les encapsulations telles que vector et deque. Pour les autres objets, il est très rarement nécessaire de les stocker sur le tas - utilisez simplement les paramètres locaux et la composition d'objet. Néanmoins, le besoin de pointeurs automatiques se pose avec les fonctions qui renvoient des pointeurs de tas, tels que les usines et les retours polymorphes.

Les pointeurs partagés suppriment la pointe lorsque le dernier pointeur partagé est détruit. Ceci est utile lorsque vous souhaitez un système de stockage à la fois simple et intuitif, dans lequel la durée de vie et la propriété prévues peuvent varier considérablement selon la situation. En raison de la nécessité de conserver un compteur (atomique), ils sont un peu plus lents que les pointeurs automatiques. Certains disent à moitié en plaisantant que les pointeurs partagés sont destinés aux personnes qui ne peuvent pas concevoir de systèmes - jugez par vous-même.

Pour obtenir une contrepartie essentielle aux pointeurs partagés, recherchez également les pointeurs faibles.

32
Iraimbilanja

Les pointeurs intelligents se nettoient d'eux-mêmes lorsqu'ils disparaissent (supprimant ainsi la peur de la plupart des fuites de mémoire). Les pointeurs partagés sont des pointeurs intelligents qui conservent le nombre d'instances du pointeur existantes et nettoient la mémoire uniquement lorsque le nombre atteint zéro. En général, utilisez uniquement des pointeurs partagés (mais veillez à utiliser le bon type - il en existe un différent pour les tableaux). Ils ont beaucoup à faire avec RAII .

21
Sydius

Pour éviter les fuites de mémoire, vous pouvez utiliser des pointeurs intelligents chaque fois que vous le pouvez. Il existe essentiellement 2 types de pointeurs intelligents en C++

  • Référence comptée (par exemple, boost :: shared_ptr/std :: tr1: shared_ptr)
  • non référence comptée (par exemple boost :: scoped_ptr/std :: auto_ptr)

La principale différence est que les pointeurs intelligents comptés en référence peuvent être copiés (et utilisés dans std :: containers) alors que scoped_ptr ne le peut pas. Les pointeurs non comptés en référence n'ont presque pas de surcoût, voire aucun. Le comptage des références introduit toujours une sorte de surcharge.

(Je suggère d'éviter auto_ptr, car il est mal utilisé s'il est mal utilisé)

8
d0k

Pour ajouter un petit détail à la réponse de Sydius, les pointeurs intelligents constitueront souvent une solution plus stable en détectant de nombreuses erreurs faciles à commettre. Les pointeurs bruts auront quelques avantages en termes de performance et peuvent être plus flexibles dans certaines circonstances. Vous pouvez également être obligé d'utiliser des pointeurs bruts lors de la liaison à certaines bibliothèques tierces.

5
SmacL