Pointeurs intelligents: à qui appartient l'objet? [fermé]

114

C ++ est tout au sujet de la propriété de la mémoire - aka sémantique de propriété .

Il est de la responsabilité du propriétaire d'un morceau de mémoire allouée dynamiquement de libérer cette mémoire. La question est donc de savoir à qui appartient la mémoire.

En C ++, la propriété est documentée par le type dans lequel un pointeur brut est enveloppé.Par conséquent, dans un bon programme C ++ (IMO), il est très rare ( rare , pas jamais ) de voir des pointeurs bruts passés (car les pointeurs bruts n'ont pas de propriété déduite, nous pouvons donc ne pas dire à qui appartient la mémoire et donc sans une lecture attentive de la documentation, vous ne pouvez pas dire qui est responsable de la propriété).

Inversement, il est rare de voir des pointeurs bruts stockés dans une classe, chaque pointeur brut est stocké dans son propre wrapper de pointeur intelligent. ( NB: si vous ne possédez pas un objet, vous ne devriez pas le stocker car vous ne pouvez pas savoir quand il sortira de sa portée et sera détruit.)

Donc la question:

  • Quel type de sémantique de propriété les gens ont-ils rencontrés?
  • Quelles classes standard sont utilisées pour implémenter cette sémantique?
  • Dans quelles situations les trouvez-vous utiles?

Permet de conserver 1 type de propriété sémantique par réponse afin qu'ils puissent être votés individuellement.

Résumé:

Conceptuellement, les pointeurs intelligents sont simples et une mise en œuvre naïve est facile. J'ai vu de nombreuses tentatives d'implémentation, mais elles sont invariablement interrompues d'une manière qui n'est pas évidente pour une utilisation occasionnelle et des exemples. Ainsi, je recommande de toujours utiliser des pointeurs intelligents bien testés à partir d'une bibliothèque plutôt que de lancer le vôtre. std::auto_ptrou l'un des pointeurs intelligents Boost semble couvrir tous mes besoins.

std::auto_ptr<T>:

Une seule personne possède l'objet. Le transfert de propriété est autorisé.

Utilisation: Cela vous permet de définir des interfaces qui montrent le transfert explicite de propriété.

boost::scoped_ptr<T>

Une seule personne possède l'objet. Le transfert de propriété n'est PAS autorisé.

Utilisation: utilisé pour montrer la propriété explicite. L'objet sera détruit par le destructeur ou lors d'une réinitialisation explicite.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Propriété multiple. Il s'agit d'un simple pointeur compté par référence. Lorsque le nombre de références atteint zéro, l'objet est détruit.

Utilisation: lorsqu'un objet peut avoir plusieurs owers avec une durée de vie qui ne peut pas être déterminée au moment de la compilation.

boost::weak_ptr<T>:

Utilisé avec shared_ptr<T>dans les situations où un cycle de pointeurs peut se produire.

Utilisation: Utilisé pour empêcher les cycles de conserver des objets lorsque seul le cycle gère un refcount partagé.

Martin York
la source
14
?? Quelle était la question?
Pacerier
9
Je voulais juste souligner que depuis que cette question a été publiée, auto_ptr a été obsolète au profit de unique_ptr (désormais standardisé)
Juan Campa
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Cela peut-il être reformulé? Je ne comprends pas du tout.
lolololol ol
@lololololol Vous coupez la phrase en deux. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. Les pointeurs RAW n'ont pas de sémantique de propriété. Si vous ne connaissez pas le propriétaire, vous ne savez pas qui est responsable de la suppression de l'objet.Il existe plusieurs classes standard qui sont utilisées pour encapsuler les pointeurs (std :: shared_ptr, std :: unique_ptr etc) qui définissent la propriété et donc définir qui est responsable de la suppression du pointeur.
Martin York
1
Dans C ++ 11 +, N'UTILISEZ PAS auto_ptr! Utilisez plutôt unique_ptr!
val dit Réintégrer Monica le

Réponses:

20

Pour moi, ces 3 types couvrent la plupart de mes besoins:

shared_ptr - comptage de référence, désallocation lorsque le compteur atteint zéro

weak_ptr- comme ci-dessus, mais c'est un 'esclave' pour un shared_ptr, impossible de désallouer

auto_ptr- lorsque la création et la désallocation se produisent à l'intérieur de la même fonction, ou lorsque l'objet doit être considéré comme un seul propriétaire. Lorsque vous affectez un pointeur à un autre, le second «vole» l'objet du premier.

J'ai ma propre implémentation pour ceux-ci, mais ils sont également disponibles au format Boost.

Je passe toujours des objets par référence (dans la constmesure du possible), dans ce cas, la méthode appelée doit supposer que l'objet n'est vivant que pendant le moment de l'appel.

J'utilise un autre type de pointeur que j'appelle hub_ptr . C'est lorsque vous avez un objet qui doit être accessible à partir d'objets imbriqués (généralement en tant que classe de base virtuelle). Cela pourrait être résolu en leur passant un weak_ptr, mais cela n'a pas shared_ptrde lui-même. Comme il sait que ces objets ne vivraient pas plus longtemps que lui, il leur passe un hub_ptr (c'est juste un wrapper de modèle vers un pointeur normal).

Fabio Ceconello
la source
2
Au lieu de créer votre propre classe de pointeur (hub_ptr), pourquoi ne pas simplement passer * ceci à ces objets et les laisser le stocker comme référence? Puisque vous reconnaissez même que les objets seront détruits en même temps que la classe propriétaire, je ne comprends pas l'intérêt de sauter à travers autant de cerceaux.
Michel
4
C'est essentiellement un contrat de conception pour clarifier les choses. Lorsque l'objet enfant reçoit le hub_ptr, il sait que l'objet pointé ne sera pas détruit pendant la durée de vie de l'enfant et n'en est pas propriétaire. Les objets contenus et conteneurs conviennent d'un ensemble de règles claires. Si vous utilisez un pointeur nu, les règles peuvent être documentées, mais ne seront pas appliquées par le compilateur et le code.
Fabio Ceconello
1
Notez également que vous pouvez avoir #ifdefs pour que hub_ptr soit typé vers un pointeur nu dans les versions de version, de sorte que la surcharge n'existera que dans la version de débogage.
Fabio Ceconello
3
Notez que la documentation Boost contredit votre description de scoped_ptr. Il déclare que c'est le cas noncopyableet que la propriété ne peut être transférée.
Alec Thomas
3
@Alec Thomas, vous avez raison. Je pensais à auto_ptr et j'ai écrit scoped_ptr. Corrigée.
Fabio Ceconello
23

Modèle C ++ simple

Dans la plupart des modules que j'ai vus, par défaut, il était supposé que la réception de pointeurs ne recevait pas la propriété. En fait, les fonctions / méthodes abandonnant la propriété d'un pointeur étaient à la fois très rares et exprimaient explicitement ce fait dans leur documentation.

Ce modèle suppose que l'utilisateur n'est propriétaire que de ce qu'il alloue explicitement . Tout le reste est automatiquement éliminé (à la sortie de la portée ou via RAII). Il s'agit d'un modèle de type C, étendu par le fait que la plupart des pointeurs appartiennent à des objets qui les désalloueront automatiquement ou en cas de besoin (lors de la destruction desdits objets, principalement), et que la durée de vie des objets est prévisible (RAII est votre ami, encore).

Dans ce modèle, les pointeurs bruts circulent librement et ne sont généralement pas dangereux (mais si le développeur est suffisamment intelligent, il utilisera des références à la place chaque fois que possible).

  • pointeurs bruts
  • std :: auto_ptr
  • boost :: scoped_ptr

Modèle C ++ pointé intelligent

Dans un code rempli de pointeurs intelligents, l'utilisateur peut espérer ignorer la durée de vie des objets. Le propriétaire n'est jamais le code utilisateur: c'est le pointeur intelligent lui-même (RAII, encore une fois). Le problème est que les références circulaires mélangées à des pointeurs intelligents comptés par référence peuvent être mortelles , vous devez donc gérer à la fois des pointeurs partagés et des pointeurs faibles. Vous avez donc toujours la propriété à considérer (le pointeur faible pourrait bien ne pointer vers rien, même si son avantage par rapport au pointeur brut est qu'il peut vous le dire).

  • boost :: shared_ptr
  • boost :: low_ptr

Conclusion

Peu importe les modèles que je décris, à moins exception, la réception d' un pointeur est pas Récipiendaire de sa propriété et il est encore très important de savoir qui est propriétaire qui . Même pour le code C ++ utilisant fortement des références et / ou des pointeurs intelligents.

Paercebal
la source
10

Je n'ai pas de propriété partagée. Si vous le faites, assurez-vous que ce n'est qu'avec du code que vous ne contrôlez pas.

Cela résout 100% des problèmes, car cela vous oblige à comprendre comment tout interagit.

MSN
la source
2
  • Propriété partagée
  • boost :: shared_ptr

Lorsqu'une ressource est partagée entre plusieurs objets. Le boost shared_ptr utilise le comptage de références pour s'assurer que la ressource est désallouée lorsque tout le monde est terminé.

Martin York
la source
2

std::tr1::shared_ptr<Blah> est souvent votre meilleur pari.

Matt Cruikshank
la source
2
shared_ptr est le plus courant. Mais il y en a beaucoup plus. Chacun a son propre modèle d'utilisation et les bons et mauvais endroits pour intenter des poursuites. Un peu plus de description serait bien.
Martin York
Si vous êtes coincé avec un compilateur plus ancien, boost :: shared_ptr <blah> est ce sur quoi std :: tr1 :: shared_ptr <blah> est basé. C'est une classe suffisamment simple pour que vous puissiez probablement l'extraire de Boost et l'utiliser même si votre compilateur n'est pas pris en charge par la dernière version de Boost.
Branan le
2

De boost, il y a aussi le conteneur de pointeur bibliothèque de . Ceux-ci sont un peu plus efficaces et plus faciles à utiliser qu'un conteneur standard de pointeurs intelligents, si vous n'utilisez les objets que dans le contexte de leur conteneur.

Sous Windows, il existe les pointeurs COM (IUnknown, IDispatch et amis) et divers pointeurs intelligents pour les gérer (par exemple, le CComPtr de l'ATL et les pointeurs intelligents générés automatiquement par l'instruction "import" dans Visual Studio basé sur la classe _com_ptr ).

Ryan Ginstrom
la source
1
  • Un propriétaire
  • boost :: scoped_ptr

Lorsque vous avez besoin d'allouer de la mémoire dynamiquement mais que vous voulez être sûr qu'elle est désallouée à chaque point de sortie du bloc.

Je trouve cela utile car il peut facilement être remis en place et relâché sans jamais avoir à se soucier d'une fuite

Pieter
la source
1

Je ne pense pas avoir jamais été en mesure de partager la propriété de ma conception. En fait, du haut de ma tête, le seul cas valable auquel je puisse penser est le modèle Flyweight.

Nemanja Trifunovic
la source
1

yasper :: ptr est une alternative légère, semblable à boost :: shared_ptr. Cela fonctionne bien dans mon (pour l'instant) petit projet.

Dans la page Web à http://yasper.sourceforge.net/, il est décrit comme suit:

Pourquoi écrire un autre pointeur intelligent C ++? Il existe déjà plusieurs implémentations de pointeurs intelligents de haute qualité pour C ++, notamment le panthéon du pointeur Boost et le SmartPtr de Loki. Pour une bonne comparaison des implémentations de pointeurs intelligents et quand leur utilisation est appropriée, veuillez lire The New C ++: Smart (er) Pointers de Herb Sutter. Contrairement aux fonctionnalités étendues d'autres bibliothèques, Yasper est un pointeur de comptage de références étroitement ciblé. Il correspond étroitement aux politiques shared_ptr de Boost et RefCounted / AllowConversion de Loki. Yasper permet aux programmeurs C ++ d'oublier la gestion de la mémoire sans introduire les grandes dépendances de Boost ou avoir à se renseigner sur les modèles de stratégie compliqués de Loki. Philosophie

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Le dernier point peut être dangereux, car yasper permet des actions risquées (mais utiles) (telles que l'affectation à des pointeurs bruts et la libération manuelle) interdites par d'autres implémentations. Attention, n'utilisez ces fonctionnalités que si vous savez ce que vous faites!

Hernán
la source
1

Il existe une autre forme fréquemment utilisée de propriétaire unique transférable, et elle est préférable auto_ptrcar elle évite les problèmes causés par auto_ptrla corruption insensée de la sémantique des affectations.

Je parle de nul autre que swap. Tout type avec une swapfonction appropriée peut être conçu comme une référence intelligente à un contenu, qu'il possède jusqu'à ce que la propriété soit transférée à une autre instance du même type, en les échangeant. Chaque instance conserve son identité mais est liée à un nouveau contenu. C'est comme une référence rebindable en toute sécurité.

(C'est une référence intelligente plutôt qu'un pointeur intelligent, car vous n'avez pas à le déréférencer explicitement pour accéder au contenu.)

Cela signifie que auto_ptr devient moins nécessaire - il n'est nécessaire que pour combler les lacunes là où les types n'ont pas une bonne swapfonction. Mais tous les conteneurs std le font.

Daniel Earwicker
la source
Peut-être que cela devient moins nécessaire (je dirais que scoped_ptr le rend moins nécessaire que cela), mais cela ne disparaît pas. Avoir une fonction d'échange ne vous aide pas du tout si vous allouez quelque chose sur le tas et que quelqu'un le lance avant de le supprimer, ou si vous oubliez simplement.
Michel
C'est exactement ce que j'ai dit dans le dernier paragraphe.
Daniel Earwicker
0
  • Un propriétaire: Aka supprimer lors de la copie
  • std :: auto_ptr

Lorsque le créateur de l'objet veut céder explicitement la propriété à quelqu'un d'autre. C'est aussi une façon de documenter dans le code que je vous donne et je ne le suit plus, alors assurez-vous de le supprimer lorsque vous avez terminé.

Martin York
la source