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:
std::unique_ptr
. L’appelant peut l’assigner à un std::shared_ptr
s’il le souhaite. 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
. 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. nullptr
. La question “Quand devrais-je utiliser shared_ptr
et quand dois-je utiliser des pointeurs bruts?” a une réponse très simple:
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). 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:
std::unique_ptr<>
est un bon choix. Souvent le cas avec les fonctions d’usine. 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.
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.