Quand utiliser shared_ptr et quand utiliser des pointeurs bruts?

class B; class A { public: A () : m_b(new B()) { } shared_ptr GimmeB () { return m_b; } private: shared_ptr m_b; }; 

Disons que B est une classe qui ne devrait pas exister sémantiquement en dehors de la durée de vie de A, c’est-à-dire qu’elle n’a absolument aucun sens. GimmeB doit- GimmeB retourner un shared_ptr ou un B* ?

En général, est-il recommandé d’éviter complètement d’utiliser des pointeurs bruts en code C ++, à la place des pointeurs intelligents?

Je suis d’avis que shared_ptr ne devrait être utilisé que lorsqu’il existe un transfert explicite ou un partage de propriété, ce qui est assez rare en dehors des cas où une fonction alloue de la mémoire, la remplit avec certaines données et la renvoie. compréhension entre l’appelant et l’appelé que le premier est maintenant “responsable” de ces données.

Votre parsing est tout à fait correcte, je pense. Dans cette situation, je retournerais aussi un B* nu, ou même un [const] B& si l’object est garanti pour ne jamais être nul.

Ayant eu le temps de parcourir les pointeurs intelligents, je suis arrivé à quelques directives qui me disent quoi faire dans de nombreux cas:

  • Si vous retournez un object dont la durée de vie doit être gérée par l’appelant, renvoyez std::unique_ptr . L’appelant peut l’assigner à un std::shared_ptr s’il le souhaite.
  • Le retour de std::shared_ptr est en fait assez rare, et lorsque cela est logique, il est généralement évident que vous indiquez à l’appelant qu’il prolongera la durée de vie de l’object pointé au-delà de la durée de vie de l’object qui maintenait initialement la ressource. . Renvoyer des pointeurs partagés depuis les usines ne fait pas exception: vous devez le faire, par exemple. lorsque vous utilisez std::enable_shared_from_this .
  • Vous avez très rarement besoin de std::weak_ptr , sauf lorsque vous voulez comprendre la méthode de lock . Cela a quelques utilisations, mais elles sont rares. Dans votre exemple, si la durée de vie de l’object A n’était pas déterministe du sharepoint vue de l’appelant, cela aurait été quelque chose à considérer.
  • Si vous renvoyez une référence à un object existant dont l’appelant ne peut pas contrôler la durée de vie, renvoyez un pointeur nu ou une référence. Ce faisant, vous dites à l’appelant qu’un object existe et qu’elle ne doit pas s’occuper de sa vie. Vous devez renvoyer une référence si vous n’utilisez pas la valeur nullptr .

La question “Quand devrais-je utiliser shared_ptr et quand dois-je utiliser des pointeurs bruts?” a une réponse très simple:

  • Utilisez des pointeurs bruts lorsque vous ne souhaitez pas que la propriété soit attachée au pointeur. Ce travail peut souvent être fait avec des références. Les pointeurs bruts peuvent également être utilisés dans certains codes de bas niveau (tels que l’implémentation de pointeurs intelligents ou l’implémentation de conteneurs).
  • Utilisez unique_ptr ou scope_ptr lorsque vous souhaitez posséder un object unique. C’est l’option la plus utile et devrait être utilisée dans la plupart des cas. La propriété unique peut également être exprimée en créant simplement un object directement, plutôt que d’utiliser un pointeur (c’est même mieux que d’utiliser un unique_ptr , si cela peut être fait).
  • Utilisez shared_ptr ou intrusive_ptr lorsque vous souhaitez partager la propriété du pointeur. Cela peut être déroutant et inefficace et n’est souvent pas une bonne option. La propriété partagée peut être utile dans certaines conceptions complexes, mais devrait être évitée en général, car elle conduit à un code difficile à comprendre.

shared_ptr s exécute une tâche totalement différente des pointeurs bruts, et ni shared_ptr s ni les pointeurs bruts ne sont la meilleure option pour la majorité du code.

Ce qui suit est une bonne règle de base:

  • Lorsqu’il n’y a pas de transfert de propriété partagée ou de propriété partagée, les références ou les indicateurs simples sont suffisants. (Les pointeurs simples sont plus flexibles que les références.)
  • Lorsqu’il y a transfert de propriété mais pas de propriété partagée, std::unique_ptr<> est un bon choix. Souvent le cas avec les fonctions d’usine.
  • Lorsqu’il y a une propriété partagée, alors c’est un bon cas pour std::shared_ptr<> ou boost::intrusive_ptr<> .

Il est préférable d’éviter la propriété partagée, en partie parce qu’ils sont plus coûteux en termes de copie et std::shared_ptr<> prend le double du stockage d’un pointeur simple, mais surtout parce qu’ils sont propices à de mauvaises conceptions pas de propriétaires clairs, ce qui, à son tour, conduit à une boule de poils d’objects qui ne peuvent pas être détruits parce qu’ils détiennent des pointeurs partagés entre eux.

La meilleure conception est celle où la propriété claire est établie et hiérarchique, de sorte que, idéalement, aucun pointeur intelligent n’est requirejs. Par exemple, si une fabrique crée des objects uniques ou retourne des objects existants, il est logique que la fabrique possède les objects qu’elle crée et les conserve simplement par valeur dans un conteneur associatif (tel que std::unordered_map ), de sorte que il peut renvoyer des pointeurs simples ou des références à ses utilisateurs. Cette fabrique doit avoir une durée de vie qui commence avant son premier utilisateur et se termine après son dernier utilisateur (la propriété hiérarchique), afin que les utilisateurs ne puissent pas avoir un pointeur sur un object déjà détruit.

Si vous ne voulez pas que l’appelé de GimmeB () puisse prolonger la durée de vie du pointeur en conservant une copie du ptr après l’instance de A dies, vous ne devez certainement pas retourner un shared_ptr.

Si l’appelé n’est pas censé conserver le pointeur renvoyé pendant de longues périodes, c’est-à-dire qu’il n’y a aucun risque que l’instance de la durée de vie de A expire avant celle du pointeur, alors le pointeur brut serait meilleur. Mais même un meilleur choix consiste simplement à utiliser une référence, à moins qu’il y ait une bonne raison d’utiliser un pointeur brut réel.

Et enfin, si le pointeur renvoyé peut exister après l’expiration de la durée de vie de l’instance A, mais que vous ne voulez pas que le pointeur prolonge la durée de vie du B, vous pouvez renvoyer un faiblesse, que vous pouvez utiliser pour tester si elle existe toujours.

L’essentiel est qu’il existe généralement une solution plus intéressante que l’utilisation d’un pointeur brut.

Je suis d’accord avec vous pour dire que shared_ptr est mieux utilisé lorsque le partage explicite des ressources se produit, mais il existe d’autres types de pointeurs intelligents disponibles.

Dans votre cas précis: pourquoi ne pas renvoyer une référence?

Un pointeur suggère que les données pourraient être nulles, mais ici, il y aura toujours un B dans votre A , donc il ne sera jamais nul. La référence affirme ce comportement.

Cela étant dit, j’ai vu des gens préconiser l’utilisation de shared_ptr même dans des environnements non partagés, et donner des handles weak_ptr à l’idée de “sécuriser” l’application et d’éviter les pointeurs obsolètes. Malheureusement, étant donné que vous pouvez récupérer un shared_ptr partir de weak_ptr (et que c’est le seul moyen de manipuler les données), il rest la propriété partagée, même si ce n’était pas censé l’être.

Note: il y a un bogue subtil avec shared_ptr , une copie de A partagera le même B que l’original par défaut, sauf si vous écrivez explicitement un constructeur de copie et un opérateur d’affectation de copie. Et bien sûr, vous n’utiliseriez pas un pointeur brut dans A pour contenir un B , voulez-vous :)?


Bien sûr, une autre question est de savoir si vous devez réellement le faire. L’ encapsulation est l’un des principes du bon design. Pour réaliser l’encapsulation:

Vous ne devez pas retourner les poignées à vos internes (voir Loi de Déméter ).

La vraie réponse à votre question est peut-être que, au lieu de donner une référence ou un pointeur sur B , elle ne devrait être modifiée que par l’interface de A

Généralement, j’éviterais d’utiliser des pointeurs bruts dans la mesure du possible car ils ont une signification très ambiguë – vous pourriez devoir désallouer les pointes, mais peut-être pas, et seule la documentation lue et écrite par l’homme vous indique le cas. Et la documentation est toujours mauvaise, obsolète ou mal comprise.

Si la propriété est un problème, utilisez un pointeur intelligent. Sinon, j’utiliserais une référence si possible.

  1. Vous atsortingbuez B à la construction de A.
  2. Vous dites que B ne devrait pas persister à l’extérieur.
    Ces deux éléments indiquent que B est membre de A et renvoie simplement un accesseur de référence. Êtes-vous trop ingénieur?

Il est recommandé d’éviter d’utiliser des pointeurs bruts, mais vous ne pouvez pas tout remplacer par shared_ptr . Dans l’exemple, les utilisateurs de votre classe supposeront qu’il est acceptable d’étendre la durée de vie de B au-delà de celle de A et peuvent décider de conserver l’object B renvoyé pendant un certain temps pour leurs propres raisons. Vous devez renvoyer un weak_ptr ou, si B ne peut absolument pas exister lorsque A est détruit, une référence à B ou simplement un pointeur brut.

Quand vous dites: “Disons que B est une classe qui ne devrait pas exister sémantiquement en dehors de la durée de vie de A”

Cela me dit que B ne devrait pas exister logiquement sans A, mais qu’en est-il physiquement? Si vous pouvez être sûr que personne n’essaiera d’utiliser un * B après A dtors, un pointeur brut sera peut-être correct. Sinon, un pointeur plus intelligent peut être approprié.

Lorsque les clients ont un pointeur direct sur A, vous devez vous assurer qu’ils le géreront correctement. n’essaye pas de le dénigrer etc.

J’ai trouvé que les directives de base C ++ donnent des indications très utiles pour cette question:

Pour utiliser un pointeur brut (T *) ou un pointeur plus intelligent, il faut savoir à qui appartient l’object (dont la responsabilité est de libérer la mémoire de l’object).

posséder :

 smart pointer, own 

ne pas posséder:

 T*, T&, span<> 

own <>, span <> est défini dans la bibliothèque Microsoft GSL

voici les règles de base:

1) ne jamais utiliser de pointeur brut (ou ne pas posséder les types) pour transmettre la propriété

2) le pointeur intelligent ne doit être utilisé que lorsque la sémantique de propriété est prévue

3) T * ou le propriétaire désigne un object individuel (uniquement)

4) utiliser le vecteur / tableau / span pour le tableau

5) À mon sens, shared_ptr est généralement utilisé lorsque vous ne savez pas qui publiera l’object, par exemple, un obj est utilisé par plusieurs threads.