std :: shared_ptr en dernier recours?

60

Je regardais simplement les flux "Going Native 2012" et j'ai remarqué la discussion à ce sujet std::shared_ptr. J'ai été un peu surpris d'entendre le point de vue un peu négatif de Bjarne std::shared_ptret son commentaire qu'il devrait être utilisé en "dernier recours" lorsque la durée de vie d'un objet est incertaine (ce qui, selon lui, devrait rarement être le cas).

Quelqu'un voudrait-il expliquer cela un peu plus en profondeur? Comment pouvons-nous programmer sans std::shared_ptrgérer et toujours gérer la durée de vie des objets de manière sûre ?

Ronag
la source
8
Ne pas utiliser de pointeurs? Avoir un propriétaire distinct de l'objet, qui gère la durée de vie?
Bo Persson
2
Qu'en est-il des données explicitement partagées? Il est difficile de ne pas utiliser de pointeurs. Aussi std :: shared_pointer ferait le sale "gérer la vie" dans ce cas
Kamil Klimek
6
Avez-vous envisagé d'écouter moins le conseil présenté et plus l'argumentation derrière ce conseil? Il explique assez bien le type de système dans lequel ce type de conseil fonctionnerait.
Nicol Bolas
@ NicolBolas: J'ai écouté le conseil et l'argumentation, mais je n'avais évidemment pas l'impression de l'avoir bien compris.
Ronag
A quelle heure dit-il "dernier recours"? En regardant le message à 36 minutes ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/… ), il dit qu'il se méfie de l'utilisation de pointeurs, mais il parle de pointeurs en général, pas seulement shared_ptr et unique_ptr mais même ' pointeur régulier Il implique que les objets eux-mêmes (et non les pointeurs sur des objets alloués avec new) doivent être préférés. Le morceau auquel vous pensiez plus tard dans la présentation?
Pharap

Réponses:

56

Si vous pouvez éviter la propriété partagée, votre application sera plus simple et plus compréhensible, et donc moins susceptible aux bugs introduits lors de la maintenance. Les modèles de propriété complexes ou peu clairs conduisent généralement à des couplages difficiles à suivre entre différentes parties de l'application via un état partagé qui peut ne pas être facilement traçable.

Compte tenu de cela, il est préférable d’utiliser des objets à durée de stockage automatique et d’avoir des sous-objets "valeur". À défaut, cela unique_ptrpeut constituer une bonne alternative shared_ptr, sinon ultime, à la liste des outils souhaitables.

CB Bailey
la source
5
+1 pour affirmer que l'enjeu n'est pas la techno elle-même (propriété partagée), mais les difficultés qu'elle présente pour nous, simples humains, qui devons ensuite déchiffrer ce qui se passe.
Matthieu M.
Cependant, une telle approche limitera gravement la capacité d'un programmeur à appliquer des modèles de programmation d'accès simultané à la plupart des classes de programmation orientées objet non triviales (pour des raisons de non-copie). Ce problème est soulevé dans "Going Native 2013".
Rwong
48

Le monde dans lequel vit Bjarne est très ... académique, faute d'un meilleur terme. Si votre code peut être conçu et structuré de manière à ce que les objets aient des hiérarchies relationnelles très délibérées, de sorte que les relations de propriété soient rigides et inflexibles, le code circule dans un sens (du plus bas au plus bas), et les objets ne parlent qu'aux plus bas. la hiérarchie, alors vous ne trouverez pas beaucoup besoin shared_ptr. C'est quelque chose que vous utilisez dans les rares occasions où quelqu'un doit enfreindre les règles. Mais sinon, vous pouvez simplement coller tout ce qui est dans vectors ou dans d'autres structures de données qui utilisent la sémantique de valeur, et unique_ptrs pour les choses que vous devez allouer individuellement.

C'est un monde où il fait bon vivre, mais ce n'est pas ce que vous devez faire tout le temps. Si vous ne pouvez pas organiser votre code de cette manière, car la conception du système que vous essayez de créer signifie qu'il est impossible (ou tout simplement profondément déplaisant), vous allez alors vous retrouver de plus en plus confronté à la propriété partagée des objets. .

Dans un tel système, tenir des pointeurs nus n’est pas… dangereux, mais cela soulève des questions. L'avantage shared_ptrest qu'il fournit des garanties syntaxiques raisonnables sur la durée de vie de l'objet. Peut-il être cassé? Bien sûr. Mais les gens peuvent aussi des const_castchoses; les soins de base et l'alimentation des enfants shared_ptrdevraient fournir une qualité de vie raisonnable aux objets attribués dont la propriété doit être partagée.

Ensuite, il y a weak_ptrs, qui ne peuvent pas être utilisés en l'absence de shared_ptr. Si votre système est structuré de manière rigide, vous pouvez stocker un pointeur nu sur un objet, en sachant que la structure de l'application garantit que l'objet pointé vous survivra. Vous pouvez appeler une fonction qui renvoie un pointeur sur une valeur interne ou externe (recherchez un objet nommé X, par exemple). Dans un code correctement structuré, cette fonction ne serait disponible que si la durée de vie de l'objet était supérieure à la votre; ainsi, stocker ce pointeur nu dans votre objet est très bien.

Étant donné que cette rigidité n’est pas toujours possible dans les systèmes réels, vous avez besoin d’un moyen raisonnable d’en assurer la durée de vie. Parfois, vous n'avez pas besoin de la pleine propriété. Parfois, il suffit de savoir quand le pointeur est mauvais ou bon. C’est là que l’ weak_ptrintervient. Dans certains cas, j’aurais pu utiliser un unique_ptrou boost::scoped_ptr, mais je devais utiliser un shared_ptrparce que j’avais précisément besoin de donner à quelqu'un un pointeur "volatil". Un pointeur dont la durée de vie était indéterminée, et ils pouvaient demander quand ce pointeur avait été détruit.

Un moyen sûr de survivre lorsque l'état du monde est indéterminé.

Cela aurait-il pu être fait par un appel de fonction pour obtenir le pointeur, au lieu de via weak_ptr? Oui, mais cela pourrait être plus facilement cassé. Une fonction qui retourne un pointeur nu n'a aucun moyen de suggérer syntaxiquement que l'utilisateur ne fasse pas quelque chose comme stocker ce pointeur à long terme. Le renvoi d'un shared_ptrrend également beaucoup trop facile pour quelqu'un de simplement le stocker et potentiellement prolonger la durée de vie d'un objet. Restituer un weak_ptrmessage suggère toutefois fortement que le stockage de ce que shared_ptrvous obtenez lockest une idée ... douteuse. Cela ne vous empêchera pas de le faire, mais rien en C ++ ne vous empêchera de casser du code. weak_ptrfournit une résistance minimale de faire la chose naturelle.

Maintenant, cela shared_ptrne veut pas dire que vous ne pouvez pas en abuser ; cela peut certainement. Surtout avant unique_ptr, il y avait beaucoup de cas où j'ai simplement utilisé un boost::shared_ptrparce que je devais passer un pointeur RAII ou le mettre dans une liste. Sans la sémantique de mouvement et unique_ptr, boost::shared_ptrétait la seule vraie solution.

Et vous pouvez l'utiliser dans des endroits où cela est vraiment inutile. Comme indiqué ci-dessus, une structure de code appropriée peut éliminer le besoin de certaines utilisations de shared_ptr. Mais si votre système ne peut pas être structuré en tant que tel et continue à faire ce qu’il faut, shared_ptril sera d’une grande utilité.

Nicol Bolas
la source
4
+1: Regardez, par exemple, boost :: asio. Je pense que l'idée s'étend à de nombreux domaines. Au moment de la compilation, vous ne savez peut-être pas quel widget d'interface utilisateur ou quel appel asynchrone est le dernier à abandonner un objet, et avec shared_ptr, vous n'avez pas besoin de savoir. Cela ne s'applique évidemment pas à toutes les situations, mais à un autre outil (très utile) dans la boîte à outils.
Guy Sirton
3
Commentaire un peu tardif; shared_ptrest idéal pour les systèmes où c ++ est intégré à un langage de script tel que python. L'utilisation du boost::pythoncomptage de références côté c ++ et python coopère grandement; n'importe quel objet de c ++ peut toujours être tenu en python et il ne mourra pas.
eudoxos
1
Juste pour référence, ma compréhension n’est ni l’utilisation de WebKit ni celle de Chromium shared_ptr. Les deux utilisent leurs propres implémentations de intrusive_ptr. J'en parle uniquement parce que ce sont deux exemples concrets d'applications volumineuses écrites en C ++
gman
1
@gman: Je trouve votre commentaire très trompeur, puisque l'objection de Stroustrup à shared_ptrs'appliquer également intrusive_ptr: il s'oppose à l'ensemble du concept de propriété partagée et non à une orthographe spécifique du concept. Donc , à des fins de cette question, ce sont deux exemples concrets de grandes applications qui font usage shared_ptr. (Et, en plus, ils démontrent que shared_ptrc'est utile même quand ça ne permet pas weak_ptr.)
ruakh
1
FWIW, pour contrer l’affirmation selon laquelle Bjarne vit dans le monde universitaire: dans toute ma carrière purement industrielle (co-architecture d’une bourse du G20 et architecture d’un MOG à 500 000 joueurs), j’ai vu seulement 3 cas où nous avions réellement besoin propriété partagée. Je suis à 200% avec Bjarne ici.
No-Bugs Hare
38

Je ne crois pas en avoir déjà utilisé std::shared_ptr.

La plupart du temps, un objet est associé à une collection à laquelle il appartient pendant toute sa durée de vie. Dans ce cas, vous pouvez simplement utiliser whatever_collection<o_type>ou whatever_collection<std::unique_ptr<o_type>>, cette collection étant membre d'un objet ou d'une variable automatique. Bien sûr, si vous n'avez pas besoin d'un nombre dynamique d'objets, vous pouvez simplement utiliser un tableau automatique de taille fixe.

Ni l'itération dans la collection, ni aucune autre opération sur l'objet ne nécessite une fonction d'assistance pour partager la propriété ... elle utilise l'objet, puis revient et l'appelant garantit que l'objet reste en vie pendant tout l'appel . C’est de loin le contrat le plus utilisé entre appelant et appelé.


Nicol Bolas a commenté que "Si un objet tient sur un pointeur nu et que cet objet meurt ... oups." et "Les objets doivent s'assurer que l'objet vit à travers la vie de cet objet. Seul shared_ptrpeut le faire."

Je n'achète pas cet argument. Du moins, cela ne shared_ptrrésout pas ce problème. Qu'en est-il de:

  • Si une table de hachage tient sur un objet et que le hashcode de cet objet change ... oops.
  • Si une fonction itère un vecteur et qu'un élément est inséré dans ce vecteur ... oups.

Tout comme la récupération de place, l'utilisation par défaut de shared_ptrencourage le programmeur à ne pas penser au contrat entre objets, ni entre fonction et appelant. Il est nécessaire de penser aux conditions préalables et aux conditions postérieures correctes, et la durée de vie d’un objet n’est qu’un tout petit morceau de ce gâteau plus grand.

Les objets ne "meurent" pas, un morceau de code les détruit. Et jeter shared_ptrle problème au lieu de déterminer le contrat d’appel est une fausse sécurité.

Ben Voigt
la source
17
@ronag: Je suppose que vous avez commencé à l'utiliser là où un pointeur brut aurait été préférable, car "les pointeurs bruts sont mauvais". Mais les indicateurs bruts ne sont pas mauvais . Faire en sorte que le premier pointeur appartenant à un objet devienne un pointeur brut est incorrect, car vous devez alors gérer manuellement la mémoire, ce qui n'est pas trivial en présence d'exceptions. Mais utiliser des pointeurs bruts comme des poignées ou des itérateurs est très bien.
Ben Voigt
4
@BenVoigt: Bien sûr, le problème avec les pointeurs nus est que vous ne connaissez pas la durée de vie des objets. Si un objet tient sur un pointeur nu et qu'il meurt ... oups. C'est exactement le genre de chose shared_ptret weak_ptront été conçus pour éviter. Bjarne essaie de vivre dans un monde où tout a une belle durée de vie, explicite, et tout est construit autour de cela. Et si vous pouvez construire ce monde, tant mieux. Mais ce n'est pas comme ça dans le monde réel. Les objets doivent s'assurer que l'objet vit à travers la vie de cet objet. Seulement shared_ptrpeut faire ça.
Nicol Bolas
5
@ NicolBolas: C'est une fausse sécurité. Si l'appelant d'une fonction ne fournit pas la garantie habituelle: "Cet objet ne sera pas touché par une partie externe lors de l'appel de la fonction". Les deux parties doivent alors se mettre d'accord sur le type de modifications extérieures autorisées. shared_ptrn'atténue qu'une modification extérieure spécifique, et même pas la plus courante. Et ce n'est pas la responsabilité de l'objet de s'assurer que sa durée de vie est correcte, si le contrat d'appel de fonction spécifie le contraire.
Ben Voigt
6
@NicolBolas: Si une fonction crée un objet et le retourne par un pointeur, cela doit être un unique_ptr, exprimant qu'un seul pointeur sur l'objet existe et qu'il en est le propriétaire.
Ben Voigt
6
@Nicol: Si un pointeur d'une collection est recherché, il doit probablement utiliser le type de pointeur de cette collection ou un pointeur brut si la collection contient des valeurs. S'il crée un objet et que l'appelant en souhaite un shared_ptr, il doit quand même renvoyer un unique_ptr. La conversion de unique_ptrvers shared_ptrest facile, mais l'inverse est logiquement impossible.
Ben Voigt
16

Je préfère ne pas penser en termes absolus (comme "dernier recours") mais par rapport au domaine du problème.

Le C ++ peut offrir différentes manières de gérer la durée de vie. Certains d’entre eux essaient de re-conduire les objets de manière empilée. Certains autres essaient d'échapper à cette limitation. Certains d'entre eux sont "littéraux", d'autres sont des approximations.

En fait, vous pouvez:

  1. utiliser une sémantique de valeur pure . Fonctionne pour des objets relativement petits où ce qui est important sont des "valeurs" et non des "identités", dans lesquels vous pouvez supposer que deux Personayant la même chose namesont la même personne (mieux: deux représentations d'une même personne ). La durée de vie est octroyée par la pile de machines, la fin est essentielle pour le programme (puisqu’une personne est son nom , peu importe ce Personqu’elle transporte)
  2. utiliser les objets alloués à la pile et les références ou pointeurs associés: autorise le polymorphisme et accorde la durée de vie de l'objet. Pas besoin de "pointeurs intelligents", car vous vous assurez qu'aucun objet ne peut être "pointé" par des structures qui laissent dans la pile plus longtemps que l'objet vers lequel elles pointent (créez d'abord l'objet, puis les structures qui s'y réfèrent).
  3. utilise des objets alloués au tas gérés par la pile : c'est ce que font std :: vector et tous les conteneurs, et wat std::unique_ptr(vous pouvez le penser comme un vecteur de taille 1). De nouveau, vous admettez que l’objet commence à exister (et finit par exister) avant (après) la structure de données à laquelle il se réfère.

La faiblesse de cette méthode réside dans le fait que les types et quantités d'objets ne peuvent pas varier lors de l'exécution d'appels plus profonds au niveau de la pile en fonction de l'endroit où ils sont créés. Toutes ces techniques "échouent" dans toutes les situations où la création et la suppression d'un objet sont la conséquence d'activités de l'utilisateur, de sorte que le type d'exécution de l'objet ne soit pas connu au moment de la compilation et qu'il puisse exister des sur-structures faisant référence à des objets. L'utilisateur demande à supprimer d'un appel de fonction plus profond au niveau de la pile. Dans ce cas, vous devez soit:

  • introduire une certaine discipline sur la gestion des objets et des structures de référence associées ...
  • allez quelque part vers le côté obscur de "échapper à la durée de vie basée sur la pile pure": l'objet doit partir indépendamment des fonctions qui les ont créées. Et doit partir ... jusqu'à ce qu'ils soient nécessaires .

C ++ isteslf ne dispose d'aucun mécanisme natif pour surveiller cet événement ( while(are_they_needed)), vous devez donc effectuer une approximation avec:

  1. utiliser la propriété partagée : la vie des objets est liée à un "compteur de référence": fonctionne si la "propriété" peut être organisée de manière hiérarchique, échoue là où des boucles de propriété peuvent exister. C'est ce que fait std :: shared_ptr. Et faible_ptr peut être utilisé pour casser la boucle. Cela fonctionne la plupart du temps, mais échoue dans les grandes conceptions, où de nombreux designers travaillent dans des équipes différentes et où il n’existe aucune raison claire (ce qui découle d’une exigence quelconque) pour déterminer qui possède le quoi (le cas typique est ce précédent suivant le précédent référant précédent ou suivant propriétaire précédent précédent le suivant? En cas de besoin, les solutions sont équivalentes, et dans le grand projet vous risquez de les mélanger)
  2. Utilisez un tas d'ordures : vous ne vous souciez tout simplement pas de votre vie. Vous exécutez le collecteur de temps en temps et ce qui est imparable est considéré comme "n'est plus nécessaire" et ... eh bien ... heu ... détruit? finalisé? congelé?. Il existe un certain nombre de collecteurs GC, mais je n'en trouve jamais un qui soit réellement conscient de C ++. La plupart d'entre eux libèrent de la mémoire, sans se soucier de la destruction d'objet.
  3. Utilisez un ramasse-miettes prenant en charge C ++ , avec une interface de méthodes standard appropriée. Bonne chance pour le trouver.

Pour passer de la toute première solution à la dernière, la quantité de structure de données auxiliaire requise pour gérer la durée de vie des objets augmente, au fur et à mesure que le temps passe à l’organiser et à la maintenir.

Le garbage collector a coûté, shared_ptr en a moins, unique_ptr encore moins, et les objets gérés en pile en ont très peu.

Est-ce shared_ptrle "dernier recours" ?. Non, ce n'est pas le cas: le dernier recours est celui des éboueurs. shared_ptrest en fait le std::dernier recours proposé. Mais peut être la bonne solution, si vous êtes dans la situation que j'ai expliquée.

Emilio Garavaglia
la source
9

Herb Sutter a mentionné dans une session ultérieure que chaque fois que vous copiez shared_ptr<>, un incrément / décrément imbriqué doit se produire. Sur un code multithread sur un système multicœur, la synchronisation de la mémoire n’est pas anodine. Étant donné le choix, il est préférable d’utiliser soit une valeur de pile, soit un unique_ptr<>et de passer des références ou des pointeurs bruts.

Éclipse
la source
1
Ou passez shared_ptrpar référence lvalue ou rvalue ...
ronag
8
Le fait est que, n'utilisez pas la shared_ptrsolution miracle comme solution miracle, car elle résoudra tous vos problèmes de fuite de mémoire simplement parce qu'elle est conforme à la norme. C'est un piège tentant, mais il est toujours important d'être conscient de la propriété des ressources, et à moins que cette propriété ne soit partagée, shared_ptr<>l'option n'est pas la meilleure.
Eclipse
Pour moi, c'est le détail le moins important. Voir optimisation prématurée. Dans la plupart des cas, cela ne devrait pas conduire à la décision.
Guy Sirton
1
@ gbjbaanb: oui, ils sont au niveau du processeur, mais sur un système multicœur, vous invalidez les caches et forcez les barrières de mémoire.
Eclipse
4
Dans un projet de jeu sur lequel j'ai travaillé, nous avons constaté que la différence de performances était très significative, au point qu'il nous fallait 2 types de pointeurs différents, un threadsafe, un autre non.
Kylotan
7

Je ne me souviens pas si le terme "recours" était exactement le mot qu'il a utilisé, mais je crois que le sens réel de ce qu'il a dit était le dernier "choix": conditions de propriété claires; unique_ptr, faible_ptr, shared_ptr et même les pointeurs nus ont leur place.

Une chose sur laquelle ils ont tous convenu est que nous sommes (développeurs, auteurs de livres, etc.) tous dans la "phase d'apprentissage" de C ++ 11 et que les modèles et les styles sont en cours de définition.

Herb a expliqué, à titre d'exemple, que nous devrions nous attendre à de nouvelles éditions de certains ouvrages phares du C ++, tels que Effective C ++ (Meyers) et C ++ Coding Standards (Sutter & Alexandrescu), dans deux ans, alors que l'industrie et les meilleures pratiques de C ++ 11 casseroles.

Eddie Velasquez
la source
5

Je pense qu'il en vient à comprendre qu'il est de plus en plus courant pour tout le monde d'écrire shared_ptr chaque fois qu'ils ont pu écrire un pointeur standard (comme une sorte de remplacement global), et qu'il est utilisé comme une échappatoire au lieu d'être réellement conçu ou du moins planification de la création et de la suppression d'objets.

L'autre chose que les gens oublient (à part le goulot d'étranglement de verrouillage / mise à jour / déverrouillage mentionné dans le contenu ci-dessus), c'est que shared_ptr ne résout pas à lui seul les problèmes de cycle. Vous pouvez toujours perdre des ressources avec shared_ptr:

L'objet A, contient un pointeur partagé sur un autre objet. L'objet B crée A a1 et A a2 et attribue a1.otherA = a2; et a2.otherA = a1; Maintenant, les pointeurs partagés de l'objet B utilisés pour créer a1, a2 sortent de la portée (disons à la fin d'une fonction). Maintenant, vous avez une fuite - personne d'autre ne fait référence à a1 et a2, mais ils se référent l'un à l'autre de sorte que leur nombre de références est toujours égal à 1 et que vous avez une fuite.

C'est un exemple simple. Lorsque cela se produit dans du code réel, cela se produit généralement de manière compliquée. Il y a une solution avec faible_ptr, mais tant de gens font maintenant juste du shared_ptr partout et ne connaissent même pas le problème de fuite ou même de faible_ptr.

Pour conclure, je pense que les commentaires référencés par le PO se résument à ceci:

Quelle que soit la langue dans laquelle vous travaillez (gérée, non gérée ou quelque chose entre les chiffres de référence tels que shared_ptr), vous devez comprendre et décider intentionnellement de la création, de la durée de vie et de la destruction d'objets.

edit: même si cela signifie "inconnu, je dois utiliser un shared_ptr", vous y avez encore pensé et le faites intentionnellement.

anon
la source
3

Je réponds de mon expérience avec Objective-C, un langage dans lequel tous les objets sont comptés et alloués sur le tas. Parce qu’il n’ya qu’une façon de traiter les objets, les choses sont beaucoup plus simples pour le programmeur. Cela a permis de définir des règles standard qui, une fois appliquées, garantissent la robustesse du code et l’absence de fuite de mémoire. Cela a également permis à des optimisations intelligentes du compilateur d'émerger comme le récent ARC (comptage automatique des références).

Mon point est que shared_ptr devrait être votre première option plutôt que le dernier recours. Utilisez le comptage de références par défaut et d'autres options uniquement si vous êtes certain de ce que vous faites. Vous serez plus productif et votre code sera plus robuste.

Dimitris
la source
1

Je vais essayer de répondre à la question:

Comment pouvons-nous programmer sans std :: shared_ptr tout en gérant les vies des objets de manière sécurisée?

C ++ a un grand nombre de façons différentes de faire de la mémoire, par exemple:

  1. Utilisez struct A { MyStruct s1,s2; };plutôt shared_ptr dans la portée de la classe. Cela concerne uniquement les programmeurs avancés, car cela nécessite de comprendre le fonctionnement des dépendances et de maîtriser suffisamment les dépendances pour les restreindre à une arborescence. L'ordre des classes dans le fichier d'en-tête est un aspect important de cela. Il semble que cette utilisation soit déjà commune avec les types natifs c ++ intégrés, mais son utilisation avec les classes définies par le programmeur semble être moins utilisée en raison de ces problèmes de dépendance et d’ordre des classes. Cette solution a aussi des problèmes avec sizeof. Les programmeurs voient dans les problèmes que cela pose comme une obligation d'utiliser des déclarations anticipées ou des #inclus non nécessaires. Ainsi, de nombreux programmeurs retomberont sur une solution inférieure de pointeurs, puis sur shared_ptr.
  2. Utilisez MyClass &find_obj(int i);+ clone () au lieu de shared_ptr<MyClass> create_obj(int i);. Beaucoup de programmeurs veulent créer des usines pour créer de nouveaux objets. shared_ptr est parfaitement adapté à ce type d'utilisation. Le problème est qu’elle suppose déjà une solution de gestion de la mémoire complexe utilisant l’allocation de tas / librairie, au lieu d’une solution plus simple basée sur la pile ou sur les objets. Une bonne hiérarchie de classes C ++ prend en charge tous les schémas de gestion de la mémoire, pas un seul. La solution basée sur les références peut fonctionner si l'objet renvoyé est stocké à l'intérieur de l'objet contenant au lieu d'utiliser la variable de portée de la fonction locale. Le transfert de propriété de l'usine au code utilisateur doit être évité. Copier l'objet après avoir utilisé find_obj () est un bon moyen de le gérer - les constructeurs de copie normaux et le constructeur normal (de classe différente) avec le paramètre refrerence ou clone () pour les objets polymorphes peuvent le gérer.
  3. Utilisation de références au lieu de pointeurs ou shared_ptrs. Chaque classe c ++ a des constructeurs et chaque membre de données de référence doit être initialisé. Cette utilisation peut éviter de nombreuses utilisations des pointeurs et shared_ptrs. Vous devez simplement choisir si votre mémoire est à l'intérieur ou à l'extérieur de l'objet, puis choisir la solution de struct ou la solution de référence en fonction de la décision. Les problèmes liés à cette solution sont généralement liés à l’évitement des paramètres de constructeur, pratique courante mais problématique et incompréhensible quant à la conception des interfaces pour les classes.
tp1
la source
"La propriété de l'usine au code utilisateur devrait être évitée." Et que se passe-t-il quand ce n'est pas possible? "Utilisation de références au lieu de pointeurs ou de shared_ptrs." Um non. Les pointeurs peuvent être réinstallés. Les références ne peuvent pas. Cela impose des restrictions de temps de construction sur ce qui est stocké dans une classe. Ce n'est pas pratique pour beaucoup de choses. Votre solution semble très rigide et inflexible aux besoins d’une interface et d’un schéma d’utilisation plus fluides.
Nicol Bolas
@Nicol Bolas: Une fois que vous avez suivi les règles ci-dessus, les références vont être utilisées pour les dépendances entre objets, et non pour le stockage de données, comme vous l'avez suggéré. Les dépendances sont plus stables que les données, nous n'entrons donc jamais dans le problème que vous envisagiez.
tp1
Voici un exemple très simple. Vous avez une entité de jeu, qui est un objet. Il doit faire référence à un autre objet, à savoir une entité cible à laquelle il doit parler. Cependant, les objectifs peuvent changer. Les cibles peuvent mourir à différents moments. Et l'entité doit être capable de gérer ces circonstances. Votre approche sans pointeur rigide ne peut pas gérer même quelque chose d'aussi simple que de changer de cible, sans parler de la mort de la cible.
Nicol Bolas
@ nicol bolas: oh, c'est traité différemment; l'interface de la classe prend en charge plusieurs "entités". Au lieu d'un mappage 1: 1 entre objets et entités, vous utiliserez entityarray. Ensuite, les entités meurent très facilement en les retirant simplement du tableau. Il n'y a qu'un petit nombre d'entités dans le jeu et les dépendances entre les tableaux ne changent pas très souvent :)
tp1
2
Non, unique_ptrconvient le mieux aux usines. Vous pouvez transformer un unique_ptren un shared_ptr, mais il est logiquement impossible d'aller dans l'autre sens.
Ben Voigt