7
votes

Devrais-je utiliser un pointeur intelligent?

J'ai une classe comme ce qui suit: xxx

dois-je utiliser un pointeur intelligent au lieu des pointeurs bruts? Pourquoi? Si oui, quel type de pointeur intelligent?


0 commentaires

3 Réponses :


5
votes

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;
};


11 commentaires

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 à 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 , c'est un bon pari que partagé_ptr n'était pas une bonne décision de conception. Et à tout moment, vous mélangez Shared_ptr et des pointeurs bruts (y compris ceci ), vous demandez des problèmes. si 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 ), utilisez ensuite intrusion_ptr , pas partagé_ptr .


@Jameskanze: Je suis d'accord sur le fait que le recours à faibly_ptr 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 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 x (nouveau t (...)) Scénario est assez pare-balles. Même pour les arguments de la fonction, il y a l'ordre d'évaluation habituel hilarité et la norme gentiment laissée make_unique (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 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 et t * devrait vraiment coder Production C ++. Sans parler de ce que le compilateur peut vous protéger de presque 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 l'avoir déplacé dans l'arbre.


@Jameskanze: Plus compliqué? C'est une insertion simple pour partagée_ptr - beaucoup plus simple que la version du pointeur brut, bien sûr - et un simple std :: déplacer pour unique_ptr . 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 , et ils fonctionnent simplement bien.



16
votes

Utilisez toujours un pointeur intelligent où que vous possédez des ressources (mémoire, fichiers, etc.). Les posséder manuellement sont extrêmement error sujet et viole beaucoup bonnes pratiques, comme Dry .

Lequel utiliser dépend de la sémantique de propriété dont vous avez besoin. unique_ptr est le meilleur pour la propriété unique et partagé_ptr Propriété partagée.

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, unique_ptr fonctionne mieux ici.

Il est également notable que Quoi sur Terre , une liste liée des pointeurs? Ça n'a aucun sens. Pourquoi pas une liste de valeurs liée?


32 commentaires

Le problème ici est qu'il est fortement improbable que nœud possède toutes les ressources. Dans ce cas, la propriété appartient probablement à la classe contenant ( arborescence ou autre), même s'il ne contient pas de pointeurs directs sur l'objet. Utilisation de unique_ptr ici, bien sûr, est complètement erroné et partagé_ptr ne fonctionne que si vous lancez dans certains faibly_ptr , 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 de cette structure pour être moins fiable ou plus complexe que la version basée sur le pointeur brut. En fait, unique_ptr est pratiquement la définition 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 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 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 ne garantit presque pas les pointeurs suspendus, et en utilisant partagé_ptr garantit des fuites de mémoire sauf si vous utilisez également faibly_ptr (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 introduit plus de points de points en suspens que Supprimer fait. Et partagé_ptr seulement 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 ont, et même très mauvaises conceptions ont très rarement et pas plus de points d'autocollants autres que Supprimer .


@Jameskanze L'utilisation d'uniques_ptr vous garantit presque des pointeurs suspendus - 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 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 .


@ 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 , pourquoi proposez-vous std :: unique_ptr ?


@Gliderkite: Vous ne montrez aucune indication que le partage de la propriété est nécessaire.


@Gliderkite: Utilisez unique_ptr 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 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 .


@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 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 et d'utiliser unique_ptr 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 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 et d'utiliser unique_ptr 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 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 vide F (t * p) {Supprimer P; } . Considérez-vous que f (p) fuites plus que Supprimer p; ? Bien sûr que non. Alors, pourquoi est-il différent si, au lieu de f , j'ai ~ unique_ptr ? C'était votre 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; 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 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 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 , 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 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 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 enfants . Mon hypothèse était que l'OP avait l'intention d'utiliser cela pour gérer les enfants, sinon il ne demanderait pas ou non utiliser des pointeurs intelligents. Étant donné que, unique_ptr 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 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 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 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 sur ad-hoc Supprimer Les expressions sont comme je l'ai dit ci-dessus: pas besoin d'écrire un destructeur, pas besoin d'un essayez .. .Catch 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 résout complètement.


@Jameskanze: Et comme je l'ai dit, si vous avez une raison convaincante pour éviter unique_ptr , 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 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 , 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 , raison pour laquelle j'ai continué à vous demander d'élaborer.



-2
votes

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.


8 commentaires

Les graphiques hiérarchiques stricts peuvent être traités correctement par std :: unique_ptr et tout DAG peut fonctionner correctement avec std :: Shared_ptr . 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 ou Shared_ptr 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 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 le type de valeur qu'il contient pour vous était gratuit .


@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 types de pointeurs que vous pourriez vous soucier de besoin (régulièrement). La décision de quand utiliser lequel est trivial et identique à la décision de lorsque vous utilisez Supprimer . 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 ) 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 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?)