J'ai une classe comme ce qui suit: dois-je utiliser un pointeur intelligent au lieu des pointeurs bruts? Pourquoi? Si oui, quel type de pointeur intelligent? P> p>
3 Réponses :
C'est toujours une bonne idée d'utiliser des pointeurs intelligents, mais méfiez-vous des boucles de références.
class node { public: std::weak_ptr<node> parent; std::list< std::shared_ptr<node> > children; };
Le pointeur parent n'a pas besoin d'être un pointeur non brut.
@Deadmg à moins qu'il ait besoin de créer un partagé_ptr code> à partir de celui-ci à un moment donné.
@Jameskanze: possible, mais pas particulièrement probable, et il n'existe pas de besoin exprimé par la propriété partagée ici, une propriété unique et un pointeur brut est le design le plus simple.
En d'autres termes, utilisez des pointeurs intelligents pour rendre le code plus obscurcissant et moins fiable que si vous avez utilisé des pointeurs bruts. Chaque fois que vous devez utiliser faible_ptr code>, c'est un bon pari que
partagé_ptr code> n'était pas une bonne décision de conception. Et à tout moment, vous mélangez
Shared_ptr code> et des pointeurs bruts (y compris
ceci code>), vous demandez des problèmes. si i> une sorte de comptage de référence est approprié (ce qui est rarement le cas, et jamais le cas lorsqu'il s'agit de structures de type graphique), et que vous avez besoin de pointes bruts également (comme
ceci Code>), utilisez ensuite
intrusion_ptr code>, pas
partagé_ptr code>.
@Jameskanze: Je suis d'accord sur le fait que le recours à faibly_ptr code> est généralement une mauvaise chose, mais je ne vois pas du tout comment l'utilisation des pointeurs intelligents dans ce cas est plus obscopé ou moins fiable que la version du pointeur brut. La version intelligente du pointeur est identique à tous égards, sauf qu'il garantit une protection contre les doubles suppressions, les fuites de mémoire et d'autres mauvais comportements dont le code basé sur le pointeur brut présente tout le temps.
@Deadmg Mélanger Les types de pointeur devient rapidement très déroutant. unique_ptr code> finira par être une source sûre d'erreurs ici, soit parce que la capture sera trop tardive (résultant d'une fuite de mémoire) ou trop tôt (résultant d'un pointeur étant invalidé quand il ne devrait pas être) .
@Deadmg La version Smart Pointer est identique, sauf qu'il en résulte une confusion entourant les différents types de pointeurs et des erreurs supplémentaires dues à cette confusion. (Les pointeurs intelligents peuvent être appropriés lors de la construction des nœuds, libérant le pointeur une fois que le nœud est fermement dans l'arborescence.)
@Jameskanze: Il est impossible de la capturer trop tard. Le simple unique_ptr
make_unique code> (accepté comme défaut), mais vous pouvez écrire votre propre qui est garantie, même dans ce scénario. Comme pour trop tôt, le
unique_ptr code> ne relâchera pas avant de vous, le programmeur, supprimez-le. Cela se produit à la durée exacte exacte comme en supprimant simplement le pointeur de la liste, puis en le supprimant.
Comme pour les confusions du pointeur, personne qui ne peut pas dire la différence entre partagé_ptr
unique_ptr
t * code> devrait vraiment coder Production C ++. Sans parler de ce que le compilateur peut vous protéger de presque i> toutes les erreurs d'utilisation de ces pointeurs - et non quelque chose que vous pouvez dire des pointeurs bruts.
@Deadmg Il est impossible de capturer trop tard si vous avez une politique de capture trop tôt. Les pointeurs de déplacement d'un nœud à l'autre, par exemple, deviennent plus compliqués lorsque des pointeurs intelligents sont utilisés et que vous avez également des problèmes concernant le propriétaire lors de la création d'un nouveau nœud; Selon la logique, il pourrait facilement avoir de sens à finir de remplir les éléments de données après i> l'avoir déplacé dans l'arbre.
@Jameskanze: Plus compliqué? C'est une insertion simple pour partagée_ptr code> - beaucoup plus simple que la version du pointeur brut, bien sûr - et un simple
std :: déplacer code> pour
unique_ptr code>. Il n'y a pas de complexité significative là-bas. En ce qui concerne le remplissant après le déplacer dans l'arbre, alors dites simplement une référence non propriétaire de l'insertion. C'est exactement le comportement des conteneurs standard tels que
std :: mappez code>, et ils fonctionnent simplement bien.
Utilisez toujours un pointeur intelligent où que vous possédez des ressources (mémoire, fichiers, etc.). Les posséder manuellement sont extrêmement em> error sujet et viole beaucoup em> bonnes pratiques, comme Dry . P>
Lequel utiliser dépend de la sémantique de propriété dont vous avez besoin. Alors que les enfants ne possèdent pas leurs parents, un pointeur de parent cru va bien. Cependant, si les parents possèdent leurs enfants, Il est également notable que Quoi sur Terre em>, une liste liée des pointeurs? Ça n'a aucun sens. Pourquoi pas une liste de valeurs liée? P> unique_ptr code> est le meilleur pour la propriété unique et
partagé_ptr code> Propriété partagée. P>
unique_ptr code> fonctionne mieux ici. P>
Le problème ici est qu'il est fortement improbable que nœud code> possède toutes les ressources. Dans ce cas, la propriété appartient probablement à la classe contenant (
arborescence code> ou autre), même s'il ne contient pas de pointeurs directs sur l'objet. Utilisation de
unique_ptr code> ici, bien sûr, est complètement erroné et
partagé_ptr code> ne fonctionne que si vous lancez dans certains
faibly_ptr code>, qui entraîne le code de code qui est moins fiable et plus complexe que si vous faites la chose correctement.
@Jameskanze: Les ressources de, disent ... la mémoire des nœuds de l'enfant? Et il est tout à fait impossible pour une version basée sur cette structure code> de cette structure pour être moins fiable ou plus complexe que la version basée sur le pointeur brut. En fait, unique_ptr code> est pratiquement la définition i> de fiable et simple. Peut-être devriez-vous développer exactement pourquoi vous pensez que la version intelligente du pointeur serait pire que la version du pointeur brut.
Peut-être devriez-vous expliquer pourquoi nécessitant plusieurs types de pointeur et ajouter une complexité inutile est une amélioration. Utiliser des pointeurs intelligents ici est un anti-motif.
@Jameskanze: pas de problème. Pour un, vous, la mise en œuvre, ne doit plus supprimer manuellement les nœuds. Ceci garantit i> que vous ne pouvez pas les doubler, ou oubliez de les supprimer et garantit également la sécurité des exceptions. Et vous l'obtenez pour Supprimer I> Tout ce code qui aurait autrement supprimer. Il réduit donc la taille du code et garantit la sécurité. Cela ressemble à une victoire de tous les moyens que je puisse imaginer à moi. Comme pour différents types de pointeur, Eh bien, en C ++, vous utilisez différents types pour effectuer différents travaux. C'est un fait nécessaire de la vie. La syntaxe indique clairement ce qu'est ce que c'est quoi.
Utilisation de unique_ptr code> ne garantit presque pas les pointeurs suspendus, et en utilisant
partagé_ptr code> garantit des fuites de mémoire sauf si vous utilisez également
faibly_ptr code> (et des pointeurs suspendus si vous ne le faites pas). Faire le bon choix explicitement est beaucoup plus simple que d'essayer de supposer d'autres outils conçus à d'autres fins.
@Jameskanze: unique_ptr code> introduit plus de points de points en suspens que
Supprimer code> fait. Et
partagé_ptr code> seulement i> introduit des fuites de mémoire quand il y a des cycles de référence - quelque chose que tous les bons conceptions très rarement i> ont, et même très mauvaises conceptions ont très rarement et pas plus de points d'autocollants autres que
Supprimer code>.
@Jameskanze L'utilisation d'uniques_ptr vous garantit presque des pointeurs suspendus code> i> - J'ai du mal à voir ce que tu veux dire. Pourriez-vous indiquer un exemple?
@Jameskanze: Je dirais que les pointeurs bruts sont complètement le mauvais choix pour les nœuds d'enfants; Je préférerais ne pas avoir à faire une danse d'exception-sécurité pour ajouter une à la collection. Comme DormyMG le dit, une liste code> code> de valeurs fonctionnerait ici, il n'ya donc aucun besoin de pointeurs intelligents du tout. À moins que les enfants soient polymorphes, auquel cas unique_ptr code>.
@ Domagojpandža et où est le pointeur pendling? Droite, vous avez de bons objets automatiques emballés soignés créés de manière dynamique. (Oui, je suis au courant de la "comédie" de la réponse)
@MikeSeseymour Les enfants sont polymorphes, mais je pense que c'est mieux std :: partagé_ptr code>, pourquoi proposez-vous
std :: unique_ptr code>?
@Gliderkite: Vous ne montrez aucune indication que le partage de la propriété est nécessaire.
@Gliderkite: Utilisez unique_ptr code> si l'objet possède un seul propriétaire (ce qui est impliqué par l'héritière parent / enfant semblable à l'arborescence); C'est un pointeur simple et incordable qui est difficile à faire des erreurs et est aussi efficace qu'un pointeur brut. Utilisez
partagée_ptr code> si vous souhaitez partager la propriété, et faites très attention aux dépendances circulaires car ils peuvent facilement conduire à des fuites de mémoire, ou si vous utilisez un compilateur de la vieille école qui ne prend pas en charge
unique_ptr code>.
@MikeSeymour et utilisez-vous si le schéma de propriété ne suit pas l'une de ces règles très restrictives (qui est le cas habituel, et le cas dans l'exemple de l'OP)?
Le pointeur pendling est, bien entendu, les pointeurs bruts que vous avez laissés lorsque unique_ptr code> supprime votre objet. Les pointeurs bruts nécessaires parce que vous devez accéder à l'objet. Vous détachez un sous-arbre et tout à coup, tous les pointeurs dedans sont pendants. Avant d'avoir la chance de l'attacher ailleurs ou de faire autre chose avec elle.
@MikeSeyMour Je ne sais pas si je suis clair sur ce que vous suggérez. Si l'idée est d'avoir tous les nœuds dans un conteneur séparé, contenus dans arborescence code> et d'utiliser
unique_ptr code> dans ce conteneur si les nœuds sont polymorphes (piscine nœud, Donc parler), cela pourrait être raisonnable dans certains cas (même si je n'ai pas trouvé beaucoup de besoin de cela). Les pointeurs dans i> les nœuds doivent toujours être des pointeurs bruts, cependant.
@MikeSeyMour Je ne sais pas si je suis clair sur ce que vous suggérez. Si l'idée est d'avoir tous les nœuds dans un conteneur séparé, contenus dans arborescence code> et d'utiliser
unique_ptr code> dans ce conteneur si les nœuds sont polymorphes (piscine nœud, Donc parler), cela pourrait être raisonnable dans certains cas (même si je n'ai pas trouvé beaucoup de besoin de cela). Les pointeurs dans i> les nœuds doivent toujours être des pointeurs bruts, cependant.
@Jameskanze: Il n'y a pas de pointeurs suspendus supplémentaires avec PTR unique. Considérez une fonction comme Modèle
f (p) code> fuites plus que
Supprimer p; code>? Bien sûr que non. Alors, pourquoi est-il différent si, au lieu de
f code>, j'ai
~ unique_ptr code>? C'était votre i> le choix quant à l'effacement de cet élément et supprimez le pointeur, et ce choix se produit exactement au même moment que lorsque vous auriez appelé
supprimer PTR; code> si Vous y gérez manuellement, ce qui signifie qu'il est impossible de créer des pointeurs suspendus supplémentaires.
@Deadmg dans des cas triviaux, unique_ptr code> fonctionnera. Mais bien sûr, dans des cas triviaux, cela ne vous achète pas beaucoup. Dans des cas plus complexes, il aboutira à des pointeurs suspendus ou à un
uniques_ptr code> où ils ne doivent pas être vides. À un moment donné, des objets autres que le nœud parent doivent avoir un pointeur sur le nœud. Si ce pointeur est un
unique_ptr code>, le pointeur du parent est soudainement vide. Et s'il s'agit d'un pointeur brut, la modification de la topologie de l'arbre le fera pendre.
@Jameskanze: Eh bien, vous pouvez simplement utiliser std :: déplacer code> pour changer l'arborescence et ne pas supprimer l'objet.
@Deadmg Vous pouvez faire beaucoup de choses qui rendent le code plus difficile à lire et à comprendre. J'aime le principe du baiser --- Dans ce cas, cela signifie que l'arborescence gérer la durée de vie des nœuds, éventuellement avec une piscine de nœuds (dans le cas de très grands arbres, où les nœuds vont et vont), ou en ne faisant rien (Dans le cas où l'arbre est i> les données du programme et devriez vivre aussi longtemps que le programme; par exemple, un arbre d'analyse dans une extrémité avant du compilateur).
@James: La gestion manuelle de la mémoire est l'opposé de "SIMPLE".
@Jameskanze: Je ne sais pas où vous avez eu l'impression que je suggère une sorte de "pool de nœuds". Je déduit simplement que chaque nœud possède ses enfants et suggère qu'elle les gère avec des pointeurs uniques (puisque le polymorphisme exclut le confinement direct dans le conteneur du nœud). Cela permet d'économiser la peine d'écrire un destructeur, de manipuler une défaillance dans le conteneur et d'éviter le partage accidentel de propriété, qui sont tous nécessaires à la gestion manuelle. S'il existe un modèle d'appropriation étrange nécessitant une gestion manuelle, la question n'en fait aucune mention.
@ildjarn Il n'y a pas de véritable complexité ici: dans le cas d'un arbre, le propriétaire des nœuds est évident. Les pointeurs intelligents introduisent un nombre quelconque de contraintes supplémentaires, ce qui rend le code résultant plus complexe.
@MikeSeyMour Vous avez mentionné quelque chose à propos de tous les nœuds dans une liste. Avoir un nœud "propre" ses enfants n'est pas une bonne solution en général, car il échoue lorsque vous commencez à réorganiser l'arborescence, en déplaçant des nœuds d'un parent au prochain.
@Jameskanze: La liste est celle de la question: std :: Liste
unique_ptr code> est la bonne réponse. Si vous avez besoin de réorganiser l'arbre, c'est un cas simple de la déplacer d'un ensemble d'enfants à un autre - je ne sais pas pourquoi vous pensez que "échoue". Si vous pensez qu'un modèle de propriété différent est plus approprié, vous devriez peut-être écrire une réponse qui explique pourquoi i> Les pointeurs intelligents "ne fonctionnent pas" et "devraient être évités".
@MikeSeyMour bien, la raison principale est simplement une expérience. Mais le cas d'utilisation où unique_ptr code> ne fonctionne pas semble si évident, je suis surpris que quelqu'un lui questionne. Je l'ai décrit ci-dessus: dès que vous devez accéder aux nœuds de l'extérieur du parent (qui est presque toujours le cas), vous avez des problèmes. Et personne n'a encore été capable de montrer le moindre avantage d'utiliser un pointeur intelligent ici; Comme c'est souvent le cas, le pointeur intelligent est présenté comme une balle d'argent, lorsque (au moins dans ce cas), il crée plus de problèmes que ce qu'il résoue.
@Jameskanze: Je ne parviens pas complètement à voir comment utiliser unique_ptr code> conduit à des pointeurs suspendus de quelque manière que l'utilisation des pointeurs bruts ne le ferait pas, malgré toutes vos indices cryptiques que ses deux évidentes et inévitables. Les avantages de la gestion des objets avec
unique_ptr code> sur ad-hoc
Supprimer code> Les expressions sont comme je l'ai dit ci-dessus: pas besoin d'écrire un destructeur, pas besoin d'un
essayez .. .Catch code> Danse pour les mettre dans le conteneur et aucun risque de copier accidentellement ou de perdre un pointeur qui est censé représenter la propriété d'un objet. Ce sont des problèmes courants avec des pointeurs bruts, que
unique_ptr code> résout complètement.
@Jameskanze: Et comme je l'ai dit, si vous avez une raison convaincante pour éviter unique_ptr code>, veuillez expliquer dans une réponse. Répéter les mêmes arguments vagues encore et encore dans des commentaires deviennent plutôt fastidieux.
@MikeSeyMour Il y a plus d'un pointeur à chaque nœud. Pas seulement dans l'arbre; Lors de la traversée de l'arbre, vous aurez inévitablement des pointeurs supplémentaires à l'extérieur de l'arbre. unique_ptr code> supprime sans prendre en compte ces pointeurs. Auquel cas, ils pendent. (Le cas d'utilisation évidente est si vous déplacez des choses dans l'arbre. Vous retirez le nœud d'un parent, pour la fixer à un autre parent. Ooops, il a été supprimé.)
@Jameskanze: Ne "supprimer pas le nœud d'un parent, pour l'attacher à un autre parent"; déplacez le pointeur à la place. Tous les autres pointeurs au nœud restent valables. La seule raison de supprimer le nœud est de le supprimer de l'arborescence, auquel cas toutes les références à celui-ci sont toutefois invalidées (sauf si vous avez besoin d'un système plus compliqué, mais il n'y a aucune indication de cela ici). Je suis désolé, mais je ne peux plus voir plus à votre argument que "N'utilisez pas les pointeurs intelligents car, si vous faites les mauvaises choses avec eux, les mauvaises choses vont arriver"; qui peut être appliqué à n'importe quelle pratique.
@MikeSeymour Mais ce n'est pas mon argument contre les pointeurs intelligents ici. Mon argument est que l'introduction de complexité supplémentaire et introduit des dangers supplémentaires qui n'étaient pas présents auparavant. Si vous utilisez unique_ptr code>, par exemple, vous devez mettre en place un protocole très spécial et non intuitif pour réorganiser les nœuds de l'arborescence. Si cela vous a vraiment acheté quelque chose, cela en vaut la peine, mais dans ce cas, l'analyse des coûts et des risques se déclenche sur le côté du pointeur brut.
@Jameskanze: OK, tout cet argument n'est qu'une différence d'opinion sur ce qui constitue une "complexité" et "intuition". Je vais coller à la gestion automatique de la mémoire de la mémoire automatique et à une exception et une sémantique de propriété transférable bien définie (ce qui est destiné à moi tout simplement simple, intuitif et non spécial); Vous vous en tenez à un pointeur manuel-jongler si vous le souhaitez. Désolé d'avoir été transporté si longtemps - de vos notes de danger vagues répétées, je pensais que vous connaissiez peut-être un véritable problème d'utiliser unique_ptr code>, raison pour laquelle j'ai continué à vous demander d'élaborer.
absolument pas. Les pointeurs intelligents habituels ne fonctionnent pas avec un graphique les structures et doivent être évitées. Dans ce cas, vous avez un arbre, et Il n'y a aucun problème à gérer toutes les suppressions (et toutes les allocations) de l'objet arbre lui-même. p>
Les graphiques hiérarchiques stricts peuvent être traités correctement par std :: unique_ptr code> et tout DAG peut fonctionner correctement avec
std :: Shared_ptr code>. Au fur et à mesure que la structure de la question est une hiérarchie stricte, il n'ya aucune raison de croire que l'un des standards standard ne serait pas parfaitement correct.
@Deadmg avec un peu d'effort, vous pouvez gérer n'importe quoi. Dans ce cas, en utilisant unique_ptr code> ou
Shared_ptr code> nécessite de manière significative plus d'effort, et sont considérablement susceptibles d'erreurs, que de gérer une gestion de la mémoire dans l'arborescence code contenant code > classe.
@Jameskanze: Je ne sais pas quels efforts que vous avez à l'esprit, mais la dernière fois que j'ai vérifié, avoir le std :: list code> le type de valeur qu'il contient pour vous était gratuit i >.
@Deadmg l'effort de gestion de plusieurs types de pointeurs et d'être sûr que vous utilisez la bonne dans chaque contexte.
@Jameskanze: Il n'y a que trois trois i> types de pointeurs que vous pourriez vous soucier de besoin (régulièrement). La décision de quand utiliser lequel est trivial et identique i> à la décision de lorsque vous utilisez Supprimer code>. La seule différence est que le pointeur intelligent peut mettre en œuvre cette décision beaucoup plus fiable. Si vous ne savez pas quand supprimer un objet, vous avez un problème plus important que quel type vous utilisez aujourd'hui. Si vous le faites, alors vous savez déjà quel pointeur intelligent à utiliser.
@Deadmg Il n'y a qu'un seul type de pointeur que vous vous souciez de manière régulière: un pointeur brut. Les pointeurs intelligents sont conçus pour résoudre des problèmes particuliers; Aucun n'est approprié pour une utilisation générale. Les pointeurs intelligents les plus répandus (par exemple partagé_ptr code>) ont tous des défauts graves lorsque vous essayez de les utiliser en général.
@Jameskanze: "L'objet Y est responsable de la destruction d'objet Z, pour une définition générique de" destruct "" est un problème assez général. Oh, au cas où vous étiez curieux, je n'ai pas fini votre réponse.
@Deadmg oui. Dans ce cas, arborescence code> est responsable de la détruire ses nœuds. Même ceux à laquelle il n'a pas de pointeur. J'ai effectivement essayé de faire une utilisation systématique des pointeurs intelligents dans des applications réelles. Tout ce que j'ai eu pour celles-ci étaient des cycles et des fuites de mémoire. (Et je ne m'inquiète pas des votes en bas. C'est assez fréquent ici que des réponses incorrectes reçoivent des tonnes de prospects, alors pourquoi ne devrait-il pas être correct un vote?)