IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SL & STL C++ Discussion :

Intérêt de std::unique_ptr


Sujet :

SL & STL C++

  1. #1
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut Intérêt de std::unique_ptr
    [Ce fil de discussion a été déplacé depuis la discussion http://www.developpez.net/forums/d12...td-unique_ptr/ car il partait dans une direction différente]

    D'ailleurs, finalement, le problème ne va pas se poser, puisque je vais virer les unique_ptr de mon code.
    Ce truc ne me cause que des soucis, et a bien y réfléchir, ne m'apporte aucun, mais alors, vraiment aucun, avantage:
    _ code plus chiant à écrire
    _ erreurs de compilations encore plus longues à lire (un template de plus quoi)
    _ débogage plus lourd
    _ pas plus de protection que ça contre les delete stupides, puisqu'il faut utiliser get() qui file un pointeur brut si on veut permettre à quelqu'un d'autre (méthode, fonction, peu importe) de modifier le contenu.
    _ 0 intégration dans d'autres lib

    Bref, pour le coup, je ne vois absolument plus l'intérêt de cette indirection/abstraction supplémentaire. Du coup, je vais sûrement m'épargner un temps considérable en reprenant ma stratégie habituelle d'encapsuler moi-même mes pointeurs.
    Après tout... les pointeurs ne devraient pas me faire peur, je m'en sers depuis tellement de temps. Je me suis juste dis que l'idée paraissait bonne, mais à l'usage, je les trouve juste ridicules.

    Disons, en court, que j'ai vraiment compris cette phrase: "We can solve any problem, by introducing an extra level of indirection" que je savais ironique mais dont je ne mesurais pas totalement la portée. C'est chose faite.

    Merci tout de même pour les réponses.

  2. #2
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Freem Voir le message
    _ code plus chiant à écrire
    std::unique_ptr c'est "juste" l'implémentation de l'idée du RAII. Si tu t'en passes tu te retrouveras de toute façon à recréer ces capsules.
    Citation Envoyé par Freem Voir le message
    _ erreurs de compilations encore plus longues à lire (un template de plus quoi)
    J'ai pas testé depuis longtemps, mais il y a normalement des outils qui sont là pour rendre ces erreurs lisibles.
    Citation Envoyé par Freem Voir le message
    _ pas plus de protection que ça contre les delete stupides, puisqu'il faut utiliser get() qui file un pointeur brut si on veut permettre à quelqu'un d'autre (méthode, fonction, peu importe) de modifier le contenu.
    L'objectif de std::unique_ptr c'est d'offrir un élément permettant de clairement définir et identifier la responsabilité de la libération d'une ressource. Qu'est ce qu'un "delete stupide" ? Avec std::unique_ptr tu n'as même plus besoin de te préoccuper du delete (c'est le but du RAII).
    Citation Envoyé par Freem Voir le message
    _ 0 intégration dans d'autres lib
    Une partie des bibliothèques n'ont pas à se préoccuper de ça : elles n'ont pas de pointeur dans leur interface. Une partie de celle qui reste travaille avec des pointeurs nues pour signaler qu'elles prennent la responsabilité des données, l'absence de prises en compte des pointeurs intelligents n'est pas un problème (si la bibliothèque est destiné à avoir la responsabilité des données, alors le choix d'utiliser un pointeurs intelligent simultanément est critiquable). On est loin de 0 ...

    Pour la citation que j'ai en signature, le seul contexte où je l'ai vue c'est en introduction d'un chapitre du livre de David Abrahams sur boost::mpl, et je t'assure que c'était pas ironique.

  3. #3
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Je n'avais même pas remarqué que tu l'avais en signature
    Je l'ai vue dans divers documents, certains le prenant au sérieux, d'autres, le prenant en ironie.
    D'autres aussi s'en amusent (d'aucuns diraient qu'ils se moquent): https://en.wikipedia.org/wiki/Fundam...re_engineering
    Mais la n'est pas le sujet.

    En retrouvant un peu mon sang-froid (j'admets l'avoir perdu face à la Nième galère que me file ce truc pour un gain nul) je me dis que, peut-être, il existe une situation ou c'est utile et simplifie effectivement le travail, mais que j'ai trop voulu faire confiance à ceux qui en prônent l'usage partout, alors que dans les cas ou j'ai voulu tester ce n'est pas le cas (par chance, projet perso, donc idéal pour essayer des trucs dont on est pas trop sûrs, mais le temps perdu est toujours frustrant)

    Pour moi, je n'en vois pas encore l'intérêt. La RAII (que j'adore), je l'utilise depuis longtemps et sans pointeurs intelligents. Au final, je m'en sors aussi bien.

    Pour ce qui est des erreurs de compilation, j'utilise actuellement gcc 4.7.1-2 (debian) et c'est toujours pénible. Même si effectivement ça s'est un peu amélioré.

    Pour le "delete stupide", je veux dire que simplement unique_ptr permet de donner le pointeur contenu (unique_ptr::get()), et donc l'expose à un delete, qui risque ainsi de rendre le pointeur intelligent instable.
    Naturellement, il faut faire confiance à l'intellect de l'autre... Sauf que le but du pointeur intelligent me semblait justement d'avoir à éviter de faire confiance a quelqu'un qu'on ne connaît pas nécessairement.
    D'ailleurs, j'ai constaté certains faits intéressants au sujet de unique_ptr: dans certaines situations, j'ai été obligé de faire une boucle parcourant un vector<unique_ptr<truc>> pour faire un reset() sur chaque objet. Sans ça, je me retrouvais avec des crash à cause d'une libération de mémoire qui semblait être effectuée deux fois (de mémoire, ça remonte à pas mal de temps). Pour coup, ça fait chier de faire un destructeur avec une vulgaire boucle de delete.... Pardon, de reset() (qui, sans argument, reviens à un delete).

    Pour ce qui est librairies...
    Il se trouve qu'elles ne me semblent pas toutes si propres que ça, ou que la doc est parfois de qualité moyenne sur le point de la propriété.
    Parfois, on passe un pointeur la ou une référence suffirait, et il est donc encore à nôtre charge de nettoyer.
    Il reste toujours des restes du langage C dans les habitudes de certains, quand les références n'existaient pas. Et où ces pointeurs (ou du moins certains d'entre eux) sont utilisés de la même façon que dans scanf.

    D'ailleurs, unique_ptr est justement là pour expliciter le transfert de responsabilité, à priori. Quand on passe un unique_ptr, on est obligé d'utiliser move, et il faudrait donc être simplet pour se dire qu'on a encore la responsabilité. Donc justement, quand on prend la responsabilité d'une zone mémoire, le unique_ptr me semblait pertinent.

    Naturellement, je n'ai pas une immense expérience dans le dev, alors je me dis que je peux me planter.
    J'aimerai bien voir un projet (de petite taille ou taille moyenne, pas un truc de 3M de lignes) qui s'en sert de façon pertinente, ça me permettrait sûrement de voir là ou ça apporte quelque chose.

  4. #4
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Pour les outils, je pensais plutôt à des choses comme STLfilt, ou à clang.

    Un exemple simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    struct RessourceType1;
    struct RessourceType2;
     
    class MyClass
    {
      std::unique_ptr<RessourceType1> r1;
      std::unique_ptr<RessourceType1> r2;
     
    public:
      MyClass(/*params*/)
      {
        //Construit une ressource ou les deux selon les paramètres
      }
    };
    Le but de std::unique_ptr est bien de dire : "voici une ressource, tu en es responsable", l'utilisation de move permet effectivement de "déplacer" la responsabilité, mais si tu mets move c'est que tu veux justement la déplacer.

    Pour ton problème avec std::vector, tu as testé quand ? J'ai pas suivi l'ensemble de l'intégration de la move-semantic et des pointeurs intelligents dans gcc, mais il y a pu avoir des problèmes au début.

    Pour ce qui est de get, la philosophie de C++ c'est de laisser l'utilisateur libre, en particulier libre de se tirer dessus, si tu utilises un std::unique_ptr et que tu fais un delete p.get(); alors c'est que tu cherches les ennuies. Le choix de cette fonction get est surement du à ce que tu exprimes ensuite : des bibliothèques qui utilisent des pointeurs là où des références iraient (pour une raison X ou Y, c'est pas la question).

    Je suis d'accord avec toi sur le fait que toutes les bibliothèques ne proposent pas encore l'utilisation de pointeurs intelligents dans leur interfaces même quand ceux-ci seraient appropriés, cependant il faut minimiser ce besoin : ce sont des capsules, elles offrent de quoi les décapsuler au besoin.

  5. #5
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    clang, je n'ai pas encore réellement testé (2-3 tentatives mais je ne sais plus pourquoi je n'était pas parvenu à m'en servir et ai lâché l'affaire), c'est d'ailleurs sur ma TODO liste, vu qu'il paraît que la compilation est sensiblement plus rapide avec. STLfilt, je n'en ai même jamais entendu parler. Un item de plus pour la liste

    Pour le problème avec vector, je crois que ça remonte à quelques mois, 6 tout au plus. Il faudrait que je ré essaye, ou que je lise la norme pour connaître le comportement exact que doit avoir unique_ptr (si ça se trouve, le problème, c'est moi, vu qu'un pointeur il faut le nettoyer quand on s'en sers plus, et qu'ils ont voulu mimer au maximum le comportent d'un vrai)

    Pour le get, je sais bien que le C++ nous laisse libres. C'est d'ailleurs un peu pour ce petit plaisir maso qu'il reste mon favori. Seulement... je trouve ça tout de même paradoxal qu'un pointeur intelligent puisse permettre d'avoir le pointeur interne. Un peu comme si on pouvait manipuler la mémoire interne d'un vector en somme.
    D'un autre côté, vu qu'un certain nombre de lib n'existent pas en C++, je peux le comprendre.

    Pour le reste, minimiser le besoin... Vu que l'outil est récent, je comprend aussi qu'il ne soit pas super utilisé. Mais je pense que ce n'est pas que l'âge: de l'utilisation que j'en ai eue, il n'est pas pratique à manier.
    Il est plus simple au final (à mon goût) de faire un jeu de classes de bas niveau qui encapsulent les pointeurs nécessaires et les fonctions C-style qui peuvent les manier que d'utiliser std::unique_ptr. Et pas qu'un peu.
    L'intérêt de cette technique comparé au unique_ptr, c'est qu'on se retrouve avec la possibilité d'encapsuler les méthodes maniant le pointeur, plutôt que de continuer à faire appel à elles explicitement. Et si besoin est, il reste possible de surcharger les operateurs de cast. Ce type de classe, on les écrit une fois seulement pour chaque lib, et en utilisant l'inlining, les performances sont presque les même que du code C (reste le this à passer quand même, mais le compilo l'optimise peut-être.)
    D'autant que ça permet de dégager les dépendances à une librairie en dehors du chemin de code contenant réellement la logique du programme (et quand je vois le code de flare ou l'auteur aurait pu appeler ses pointeurs yatouquipet, je trouve vraiment cette solution élégante)

    Le souci, c'est en fin de compte le polymorphisme combiné aux conteneurs... (et sûrement un mauvais reste du C dans mes manies, vu que j'ai du mal à penser à dé-référencer un pointeur pour en faire une référence - je viens de m'en apercevoir en écrivant ce post - ).
    La solution la plus agréable aurait sûrement été de pouvoir utiliser directement des références dans les conteneurs, plutôt que de s'ennuyer à manier du pointeur (et risquer de stocker un pointeur vide. D'ailleurs, je me demande quelle est la raison de cette limitation? En interne, j'ai toujours vu les ref comme des ptr...).
    Au lieu de ça, on doit se taper des lignes dignes de JAVA, les avantages en moins (std::vector<std::unique_ptr<montemplate<maclasse>>> n'est quand même pas super causant au 1er coup d'oeil, et ce n'est qu'une déclaration.).
    Vu que personnellement, je réfléchis à 2 fois avant de mettre une donnée en protected (et 3 fois pour le public), je n'accède que rarement au contenu d'un conteneur polymorphique en dehors de l'objet qui le possède.
    Hors, quand je manie une donnée via un pointeur, je fait gaffe, je sais que ça peut péter.
    Encore une fois le remède unique_ptr me semble pire que le mal brut.

    Et pour des débutants, je ne sais pas non plus si je le recommanderais. Dire que c'est de la sécurité, c'est aussi vrai que de dire que les problèmes de mémoire sont inexistants dans les langages à GC. Sans vouloir être mesquin, c'est une fausse sécurité puisqu'elle donne un sentiment erroné, en plus de ralentir et complexifier la compilation (et les templates ne sont pas des modèles de rapidité)

    Reste plus qu'a avoir la manie d'uniformiser le code, et je me suis retrouvé à les mettre a chaque fois que je devais manier un pointeur (sont faits pour ça il paraît). Et sincèrement, je le regrette amèrement.

    En guise de conclusion de ce long post, je dirais ceci:
    Contrairement à la plupart des avis que j'ai vus, je ne le recommande pas aux débutants, parce que leur usage correct implique une vraie compréhension de la mécanique qu'ils utilisent ainsi que de std::move (parce que C++ étant axé performance, je parie que la plupart des librairies ne mettent pas le conteneur de l'objet déplacé à nullptr, ce qui peut amener des surprises).
    En plus de ça, ça donne un faux sentiment de sécurité, pas mal de prises de têtes lors de la compilation, un code moins lisible si c'est mal employé (comme je l'ai dis, je pense avoir compris une de mes erreurs en écrivant ce post, à vérifier cependant).

    En fait, mal utilisé, les avantages que j'ai souvent vus cités (à savoir, meilleure lisibilité et robustesse du code) sont vite inversés:
    _ code plus lourd, lignes plus longues (et pas qu'un peu: std::unique_ptr<> ça mesure 17 caractères! Je sais qu'on a des écrans larges mais quand même)
    _ instabilité accrue (les compilateurs visent la performance, pas la sécurité en général. Donc, le conteneur originel n'est pas remis à zéro. Son contenu reste donc valide un temps, pour péter ensuite, loin de la dernière modif)
    _ moins bonne encapsulation (on se dit qu'il n'y a plus besoin d'encapsuler ce damné pointeur puisqu'unique_ptr le fait déjà. Résultat, on peut se retrouver avec du code spécifique à une librairie dans une zone qui pourrait très bien en être dépourvue si une classe avait encapsulé les appels de fonction et la gestion du pointeur. Du coup, on perd un avantage de l'objet: la facilité de virer une lib pour en mettre une autre)

    Éventuellement... peut-être qu'un débutant C++ qui n'a jamais fait de C s'en servirait correctement.

    Et vu que je ne maîtrise pas encore le C++ je pense qu'il vaut mieux pour moi les oublier quelques temps. Le temps de penser à réfléchir à 2 fois quand je tape "*" devant une variable, en fait.
    Bref, merci de les avoir défendus, ça m'a permis de réfléchir un peu plus loin. N'empêche... Je ne m'en servirai pas de sitôt pour autant.

  6. #6
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Tu as complètement occulté l'exemple que j'ai écrit où l'utilisation de std::unique_ptr (ou boost::scoped_ptr ou équivalent) est quasi-indispensable.

    J'ai l'impression que la plupart de tes réticences sont liées à la move-semantic, mais l'utilisation de la move-semantic est loin d'être indispensable à beaucoup de code (du moins pour l'utilisateur, ce que fait l'implémentation des éléments utilisés c'est autre chose), et ne pas l'utiliser ce n'est pas s'interdire std::unique_ptr.

    Le besoin de la move-semantic avec std::unique_ptr c'est juste quand tu as besoin de déplacer la responsabilité de la durée de vie, ton design n'est pas obligé de nécessiter de tel déplacement.

    Pour ce qui est des conteneurs, je suis d'accord que ca fait une écriture à rallonge, sauf que :


    Un pointeur intelligent c'est, d'une certaine façon, un conteneur de pointeur à un seul élément (ie une capsule), tu trouves étrange de pouvoir accéder à cet élément ? Pourtant accéder aux éléments d'un conteneur ne doit pas te choquer ?

    Il est plus simple au final (à mon goût) de faire un jeu de classes de bas niveau qui encapsulent les pointeurs nécessaires et les fonctions C-style qui peuvent les manier que d'utiliser std::unique_ptr.
    C'est bien moins flexible (et c'est redondant), l'utilisation des pointeurs intelligents (plus exactement : de capsule RAII non invasive), permet d'avoir un contrôle plus fin sur les points de variation du design. Avec ta technique si tu veux changer la gestion de la ressource tu te retrouves à modifier ta classe représentant la ressource, avec une capsule style std::unique_ptr (et une bonne utilisation des typename), tu as juste à changer une ligne totalement extérieur à la classe représentant ta ressource.

    La solution la plus agréable aurait sûrement été de pouvoir utiliser directement des références dans les conteneurs, plutôt que de s'ennuyer à manier du pointeur (et risquer de stocker un pointeur vide. D'ailleurs, je me demande quelle est la raison de cette limitation? En interne, j'ai toujours vu les ref comme des ptr...)
    Regardes les std::reference_wrapper, tu pourras les mettre dans les conteneurs. Pour pointeur/référence, il est en effet probable qu'en interne l'implémentation des références et des pointeurs soit similaires car les processeurs ne doivent pas offrir de quoi faire une distinction. Par contre d'un point de vue C++ (ie la façon dont le langage les définit) la différence existe : un pointeur est un objet (au sens du modèle de la mémoire en C++) alors qu'une référence n'est qu'une variable et pas un objet. D'un point de vue sémantique, un pointeur a une sémantique de référence (dans le sens où elle peut servir à référer) mais pas seulement (ils ont une notion d'arithmétique), alors qu'une référence C++ a seulement la sémantique de référence.

    Edit: Je ne pense pas non plus que tout code doit nécessairement contenir des std::unique_ptr/boost::scoped_ptr, ca dépend du code. Par contre s'en passer là où il est approprié est, AMA, une erreur. Pour la sérialisation, j'y connais pas grand chose, cependant ce n'est pas la partie d'un design où le besoin de pointeurs intelligents me parait évident : l'important pour cet élément ce sont les données, pas qui en a la responsabilité, sérialiser les capsule-RAII est-il nécessaire ?

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Juste une réponse en passant sur un point. Autant pour un shared_ptr, accéder au pointeur contenu ne doit avoir lieu que pour de l'interfaçage avec du vieux code, autant pour un unique_ptr, je pense que ça peut intervenir plus souvent.

    En effet, côté shared, on a un couple shared_ptr/weak_ptr. Côté unique, le même couple s'écrit aujourd'hui unique_ptr/T* (et je n'ai pas réussi à vraiment convaincre les gens qu'une telle écriture n'est pas idéale).

    Il y a donc, en collaboration avec unique_ptr la possibilité tout à fait légitime et justifiée d'avoir des T* qui représentent des pointeurs n'influençant pas la durée de vie de l'objet pointé, et agissant en simples observateurs de l'unique_ptr (il faut bien entendu que l'architecture du programme permette de s'assurer que ce dernier restera vivant tant que l'observateur peut être utilisé). Et le moyen d'obtenir un T* à partir d'un unique_ptr, c'est justement get.

    Je pense que la règle à la louche n'est donc pas de faire attention aux get, ni aux pointeurs nus. Mais de faire attention aux delete. Un delete dans du code moderne, sauf s'il est dans l'implémentation d'un wrapper type smart pointer, et probablement un code smell. Pourquoi doit-on le faire manuellement, et ne risque-t-on pas de marcher sur les plates-bandes d'un autre mécanisme de gestion de la mémoire.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Tu as complètement occulté l'exemple que j'ai écrit où l'utilisation de std::unique_ptr (ou boost::scoped_ptr ou équivalent) est quasi-indispensable.
    Désolé.
    Le souci de ton exemple simple, c'est qu'il l'est trop.
    Je veux dire, il est tellement simple qu'on ne voit pas véritablement l'intérêt d'utiliser unique_ptr, excepté le fait de ne pas avoir à implémenter de destructeur.
    Du coup, je n'ai pas grand chose à dire à son sujet

    Citation Envoyé par Flob90 Voir le message
    J'ai l'impression que la plupart de tes réticences sont liées à la move-semantic, mais l'utilisation de la move-semantic est loin d'être indispensable à beaucoup de code (du moins pour l'utilisateur, ce que fait l'implémentation des éléments utilisés c'est autre chose), et ne pas l'utiliser ce n'est pas s'interdire std::unique_ptr.

    Le besoin de la move-semantic avec std::unique_ptr c'est juste quand tu as besoin de déplacer la responsabilité de la durée de vie, ton design n'est pas obligé de nécessiter de tel déplacement.
    En fait, c'est l'intérêt général de unique_ptr que je ne vois plus. Pendant un temps, je lui en trouvais un, mais ce n'est plus le cas.
    Effectivement, pour moi, si on ne déplace pas un objet d'un propriétaire a l'autre, alors les unique_ptr n'ont pas vraiment d'intérêt.
    Dans ce cas, un pointeur nu, un new dans le constructeur et un delete dans le destructeur et c'est bon.
    Utiliser un unique_ptr pour ça c'est un peu prendre un bazooka pour partir à la chasse aux mouches.

    Citation Envoyé par Flob90 Voir le message
    Pour ce qui est des conteneurs, je suis d'accord que ca fait une écriture à rallonge, sauf que :


    Un pointeur intelligent c'est, d'une certaine façon, un conteneur de pointeur à un seul élément (ie une capsule), tu trouves étrange de pouvoir accéder à cet élément ? Pourtant accéder aux éléments d'un conteneur ne doit pas te choquer ?
    Les typename, je m'en sers déjà en dehors des unique_ptr en fait. C'est bien pratique pour éviter de changer un type 20 000 fois dans le code alors que l'interface est identique.
    Mais les typename n'empêchent pas de devoir faire appel aux fonctions membres.
    Hier j'ai enlevé tous les unique_ptr de mon code, ça m'a pris pas mal de temps. Bien plus que si je m'étais amusé à convertir les vector en tableaux en durs (surtout avec la nouvelle écriture de for)
    Ce qui fait que je considère vector (par exemple) comme un progrès réel, je ne le retrouve clairement pas dans unique_ptr: lisibilité, sécurité, adaptabilité du code.

    Je ne trouve pas illogique d'accéder aux éléments d'un conteneur, mais on ne peux y accéder que de façon protégée. On ne peux pas, à ma connaissance, casser une string en jouant avec les méthodes qu'elle offre (bien qu'il soit possible de faire buguer le code utilisateur en voulant modifier le retour de c_str()). Le conteneur unique_ptr, on peut tout faire crasher, et ce, relativement facilement.
    Il suffit de faire un truc du genre "delete myUnique.get();" pour vérifier que unique_ptr ne garantit PAS qu'il est le seul à posséder la responsabilité.
    Ce problème aurait pû être évité si au lieu de renvoyer un raw ptr la méthode get() avait renvoyé un readonly_ptr.

    D'ailleurs, merci beaucoup pour le lien, à lire le paragraphe "motivation" j'ai l'impression que ça va pas mal me servir.

    Citation Envoyé par Flob90 Voir le message
    C'est bien moins flexible (et c'est redondant), l'utilisation des pointeurs intelligents (plus exactement : de capsule RAII non invasive), permet d'avoir un contrôle plus fin sur les points de variation du design. Avec ta technique si tu veux changer la gestion de la ressource tu te retrouves à modifier ta classe représentant la ressource, avec une capsule style std::unique_ptr (et une bonne utilisation des typename), tu as juste à changer une ligne totalement extérieur à la classe représentant ta ressource.
    Pour les cas ou il y a polymorphisme, c'est vrai que cette méthode n'est pas du tout optimale. Mais quand ce n'est pas le cas, il n'y a pas qu'un typedef à modifier, mais aussi tous les appels de fonction nécessitant ce pointeur.
    Des exemples où simplement utiliser unique_ptr n'offre aucune souplesse comparé à cette méthode, c'est l'usage de la SDL (écrite en C) ou de wxWidgets (C++, mais avec une certaine dose de C with classes tout de même).
    Pour la SDL, l'usage des pointeurs est évident, puisque c'est du C (notamment pour la vidéo). Pour wxWidgets, nettement moins, cependant certaines parties sont assez bancales et obligent à une manipulation mémoire assez laide (je pense au mécanisme qui gère les menus, notamment. Oh et puis non, en fait, il y a des pointeurs dans tous les sens, accompagnés de ce que je qualifierais de problèmes de conception).
    Dès lors que l'on utilise une librairie tierce, on sait que la structure ne changera pas tout le temps, et encapsuler soi-même les pointeurs imposés permet une meilleure abstraction. En fait, ça permet de changer de lib en peu de temps, sans changer les interfaces publiques. Ce que unique_ptr ne permet pas (ce n'est pas non plus son rôle).

    Citation Envoyé par Flob90 Voir le message
    Regardes les std::reference_wrapper, tu pourras les mettre dans les conteneurs. Pour pointeur/référence, il est en effet probable qu'en interne l'implémentation des références et des pointeurs soit similaires car les processeurs ne doivent pas offrir de quoi faire une distinction. Par contre d'un point de vue C++ (ie la façon dont le langage les définit) la différence existe : un pointeur est un objet (au sens du modèle de la mémoire en C++) alors qu'une référence n'est qu'une variable et pas un objet. D'un point de vue sémantique, un pointeur a une sémantique de référence (dans le sens où elle peut servir à référer) mais pas seulement (ils ont une notion d'arithmétique), alors qu'une référence C++ a seulement la sémantique de référence.
    Oui, c'est même pour ça que je n'ai rien contre les pointeurs, je sais que l'on peut jouer avec de la même façon qu'un tableau et je pense qu'ils sont également plus rapides que les itérateurs des conteneurs standards.
    Si un jour j'ai besoin d'optimiser la vitesse d'accès à des données, nul doute que je les utiliserais.
    Mais à mon humble avis, obliger l'utilisation des pointeurs pour qu'un conteneur contienne des objets polymorphes est regrettable (a voir pour reference_wrapper cela dis, je n'en avais jamais entendu parler) et même dangereux (risque de pointeur nul qui vient d'on ne sait où parce qu'utilisé 50 ans après avoir été altéré, problèmes d'allocations...).
    Bon, je suppose qu'il y a un problème technique derrière cette restriction.

    Citation Envoyé par Flob90 Voir le message
    Edit: Je ne pense pas non plus que tout code doit nécessairement contenir des std::unique_ptr/boost::scoped_ptr, ca dépend du code. Par contre s'en passer là où il est approprié est, AMA, une erreur. Pour la sérialisation, j'y connais pas grand chose, cependant ce n'est pas la partie d'un design où le besoin de pointeurs intelligents me parait évident : l'important pour cet élément ce sont les données, pas qui en a la responsabilité, sérialiser les capsule-RAII est-il nécessaire ?
    La sérialisation implique que lorsque le programme rencontre un pointeur, il sérialise aussi son contenu, et ce, de façon à respecter le fait qu'il s'agisse d'un pointeur, pas d'une instance.
    De même, la sérialisation doit accéder à la plupart des membres d'une classe.
    Ce qui fait qu'il n'y a pas 30 000 solutions: soit on est intrusifs (on modifie le code de la classe, ne serait-ce que pour ajouter un ami) et on garde un code propre (avec des membres privés donc), soit on colle tout le monde en public, ou plus probablement, on donne accès à des méthodes permettant d'interroger et d'écrire l'état de chaque membre (ce qui reviens plus ou mois au même, avec 2-3 sécurités en plus)
    Conséquence: si il y a des unique_ptr dans les membres, il faut pouvoir s'en occuper comme tels.
    Cela dis, je ne suis pas non plus un expert. C'est juste que le besoin s'est fait sentir sur mon projet d'avoir un système de sauvegarde/chargement et que du coup j'ai cherché un peu et ai fini par aboutir sur boost::serialization, qui à le mérite de pouvoir le faire de façon portable, de façon à conserver la compatibilité des fichiers entre les versions (très important pour un projet qui utilise massivement des plugin ça) et de pouvoir implémenter le format (ou type de format) que l'on veut sans modifier le code à sérialiser.



    Citation Envoyé par JolyLoic Voir le message
    Juste une réponse en passant sur un point. Autant pour un shared_ptr, accéder au pointeur contenu ne doit avoir lieu que pour de l'interfaçage avec du vieux code, autant pour un unique_ptr, je pense que ça peut intervenir plus souvent.

    En effet, côté shared, on a un couple shared_ptr/weak_ptr. Côté unique, le même couple s'écrit aujourd'hui unique_ptr/T* (et je n'ai pas réussi à vraiment convaincre les gens qu'une telle écriture n'est pas idéale).

    Il y a donc, en collaboration avec unique_ptr la possibilité tout à fait légitime et justifiée d'avoir des T* qui représentent des pointeurs n'influençant pas la durée de vie de l'objet pointé, et agissant en simples observateurs de l'unique_ptr (il faut bien entendu que l'architecture du programme permette de s'assurer que ce dernier restera vivant tant que l'observateur peut être utilisé). Et le moyen d'obtenir un T* à partir d'un unique_ptr, c'est justement get.

    Je pense que la règle à la louche n'est donc pas de faire attention aux get, ni aux pointeurs nus. Mais de faire attention aux delete. Un delete dans du code moderne, sauf s'il est dans l'implémentation d'un wrapper type smart pointer, et probablement un code smell. Pourquoi doit-on le faire manuellement, et ne risque-t-on pas de marcher sur les plates-bandes d'un autre mécanisme de gestion de la mémoire.
    Entièrement d'accord sur le fait que cette écriture n'est pas idéale. Je me permettrais même de dire que c'est une écriture dangereuse, qui favorise ce que tu appelles du code smell.
    unique_ptr me donne l'impression de ne pas être fini au final. C'est quand même marrant que le comité ait mieux protégé un outil qui devrait être utilisé de façon plus restreinte (shared_ptr qui permet à plusieurs d'avoir la responsabilité d'un objet) qu'un outil qui semble avoir une vocation à un usage plus répandu...
    Limite je trouve shared_ptr plus élégant...

  9. #9
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Freem Voir le message
    Désolé.
    Le souci de ton exemple simple, c'est qu'il l'est trop.
    Je veux dire, il est tellement simple qu'on ne voit pas véritablement l'intérêt d'utiliser unique_ptr, excepté le fait de ne pas avoir à implémenter de destructeur.
    Du coup, je n'ai pas grand chose à dire à son sujet
    Tu viens de dire exactement ce qu'il fallait Non l'utilisation de unique_ptr ici ne permet pas seulement d'avoir à ne pas écrire le destructeur, si tu penses ca alors je te propose d'écrire ce même code "simple" sans unique_ptr pour te rendre compte des problèmes (si tu ne vois aucun problème, alors postes le code, on pourra en discuter ).

    Citation Envoyé par Freem Voir le message
    Les typename, je m'en sers déjà en dehors des unique_ptr en fait. C'est bien pratique pour éviter de changer un type 20 000 fois dans le code alors que l'interface est identique.
    Mais les typename n'empêchent pas de devoir faire appel aux fonctions membres.
    Hier j'ai enlevé tous les unique_ptr de mon code, ça m'a pris pas mal de temps. Bien plus que si je m'étais amusé à convertir les vector en tableaux en durs (surtout avec la nouvelle écriture de for)
    Ce qui fait que je considère vector (par exemple) comme un progrès réel, je ne le retrouve clairement pas dans unique_ptr: lisibilité, sécurité, adaptabilité du code.
    J'indiquais l'utilisation des typename en réaction à la partie de ton message qui disait que ça faisait du code à rallonge d'utiliser des unique_ptr dans un conteneur, c'est bien uniquement le type qui est rallongé, pas les appels de fonctions, ou alors je n'ai pas compris ce que tu disais.

    Citation Envoyé par Freem Voir le message
    Je ne trouve pas illogique d'accéder aux éléments d'un conteneur, mais on ne peux y accéder que de façon protégée. On ne peux pas, à ma connaissance, casser une string en jouant avec les méthodes qu'elle offre (bien qu'il soit possible de faire buguer le code utilisateur en voulant modifier le retour de c_str()). Le conteneur unique_ptr, on peut tout faire crasher, et ce, relativement facilement.
    Il suffit de faire un truc du genre "delete myUnique.get();" pour vérifier que unique_ptr ne garantit PAS qu'il est le seul à posséder la responsabilité.
    Ce problème aurait pû être évité si au lieu de renvoyer un raw ptr la méthode get() avait renvoyé un readonly_ptr.
    Je pense que la décision première du comité pour get c'est le besoin de retro-compatibilité, donc le type de retour sera nécessairement un pointeur nue au final. Je suis d'accord avec Loic sur le fait qu'une autre capsule aurait été un meilleur choix pour réaliser le couple unique_ptr/T* qu'un pointeur nue, mais le besoin de retro-compatibilité impose le besoin de pouvoir avoir un pointeur nue.

    Au final, oui il manque peut-être quelque chose. Ça impose à l'utilisateur une certaine prudence (deux services regroupés sous le même mécanisme). Cependant la distinction entre les deux services en normalement assez clair pour que la confusion maladroite soit difficile (en général tu sais quand même si tu écris une portion de code qui a un besoin de retro-compatibilité ou non), c'est plutôt la verbosité du code qui en pâtit.

    Citation Envoyé par Freem Voir le message
    Dès lors que l'on utilise une librairie tierce, on sait que la structure ne changera pas tout le temps, et encapsuler soi-même les pointeurs imposés permet une meilleure abstraction. En fait, ça permet de changer de lib en peu de temps, sans changer les interfaces publiques. Ce que unique_ptr ne permet pas (ce n'est pas non plus son rôle).
    Je ne vois pas en quoi l'abstraction est meilleurs, je n'ai pas dit de ne pas réaliser d'interface permettant une abstraction des bibliothèques utilisés en implémentation, mais de laisser le rôle de la gestion de la durée de vie à la capsule, c'est ce que la capsule encapsule qui doit offrir l'abstraction des détails d'implémentation. Dans les deux cas l'abstraction est la même, la différence est que dans un cas les rôles sont clairs, une classes un rôle (unique_ptr : gestion de la durée de vie, ta classe : abstraction de l'implémentation, rôle précis définie selon le contexte), dans l'autre ta classe fait deux choses.

    Citation Envoyé par Freem Voir le message
    Oui, c'est même pour ça que je n'ai rien contre les pointeurs, je sais que l'on peut jouer avec de la même façon qu'un tableau et je pense qu'ils sont également plus rapides que les itérateurs des conteneurs standards.
    Si un jour j'ai besoin d'optimiser la vitesse d'accès à des données, nul doute que je les utiliserais.
    Mais à mon humble avis, obliger l'utilisation des pointeurs pour qu'un conteneur contienne des objets polymorphes est regrettable (a voir pour reference_wrapper cela dis, je n'en avais jamais entendu parler) et même dangereux (risque de pointeur nul qui vient d'on ne sait où parce qu'utilisé 50 ans après avoir été altéré, problèmes d'allocations...).
    Bon, je suppose qu'il y a un problème technique derrière cette restriction.
    Le paragraphe que j'ai fait n'est surement pas clair, mais pour faire simple, une référence n'a aucune notion d'espace mémoire (contrairement à un pointeur) dans la description du C++, ce n'est pas une entité physique en mémoire. Or l'objectif des conteneurs est de "contenir" des entités physique en mémoire (des objets), donc par définition pas des références.

  10. #10
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Tu viens de dire exactement ce qu'il fallait Non l'utilisation de unique_ptr ici ne permet pas seulement d'avoir à ne pas écrire le destructeur, si tu penses ca alors je te propose d'écrire ce même code "simple" sans unique_ptr pour te rendre compte des problèmes (si tu ne vois aucun problème, alors postes le code, on pourra en discuter ).
    Je suppose qu'initialiser les pointeur à nullptr dans le constructeur, et mettre les 2 delete dans le destructeur ne conviens pas, vue ta phrase, mais je ne vois pas en quelle situation un problème peut se poser. Constructeur par copie/opérateur d'assignement? Le problème se posera aussi pour unique_ptr, pour moi.

    Citation Envoyé par Flob90 Voir le message
    J'indiquais l'utilisation des typename en réaction à la partie de ton message qui disait que ça faisait du code à rallonge d'utiliser des unique_ptr dans un conteneur, c'est bien uniquement le type qui est rallongé, pas les appels de fonctions, ou alors je n'ai pas compris ce que tu disais.
    Le code dans l'ensemble est rallongé, pas seulement les déclarations.
    Dans le cas du programme qui m'a servi à expérimenter les unique_ptr, un logiciel de dessin vectoriel, il arrive régulièrement qu'une responsabilité soit transférée. En gros, quand je crée une shape, c'est un plugin qui l'a en charge, le temps qu'elle soit finie (ou annulée) puis la responsabilité est passée à un groupe (auquel le plugin est associé).
    Le code obtenu donne un truc dans ce goût la:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    m_target->add(std::move(m_shape));
    Bien sûr, si je n'utilise pas de unique_ptr dans le plugin, ou si j'avais besoin pour une raison X ou Y de cloner, ça aurait été dans ce goût la:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    m_target->add(std::unique_ptr<Render::Shape>(m_shape));
    simplifiable en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    m_target->add(ShapePtr(m_shape));
    mais je trouve toujours ça un peu encombrant, d'autant que dans un cas comme dans l'autre (raw ou unique ptr) il vaut mieux faire un reset/=nullptr puisque j'ai besoin de connaître l'état du pointeur dans une autre portion de code (l'utilisateur n'a pas encore cliqué, à déjà cliqué une seule fois, ou plus, pour savoir quels gestionnaires d'évènement je mets/j'enlève).
    Ou peut-être est-ce dû au fait que mes typedef aient une signification et sont régulièrement assez longs aussi (une 10aine de caractères en moyenne je dirai). Cela dis, je dois admettre ne pas les avoir utilisés pour unique_ptr.

    Citation Envoyé par Flob90 Voir le message
    Je pense que la décision première du comité pour get c'est le besoin de retro-compatibilité, donc le type de retour sera nécessairement un pointeur nue au final. Je suis d'accord avec Loic sur le fait qu'une autre capsule aurait été un meilleur choix pour réaliser le couple unique_ptr/T* qu'un pointeur nue, mais le besoin de retro-compatibilité impose le besoin de pouvoir avoir un pointeur nue.

    Au final, oui il manque peut-être quelque chose. Ça impose à l'utilisateur une certaine prudence (deux services regroupés sous le même mécanisme). Cependant la distinction entre les deux services en normalement assez clair pour que la confusion maladroite soit difficile (en général tu sais quand même si tu écris une portion de code qui a un besoin de retro-compatibilité ou non), c'est plutôt la verbosité du code qui en pâtit.
    Dans mon cas, vu que je suis le seul (à regret d'une certaine façon quoique ça me permette de faire joujou comme je veux) effectivement, l'argument de la clarté est bidon (ça, c'est dit) puisque je connais le style de celui qui a écrit le code (a moins de coder bourré ou dans mon sommeil, naturellement).
    Cela dis, ce n'est pas toujours le cas.
    Mon expérience professionnelle n'est pas spécialement très riche, mais la ou je suis j'ai vu du code "original". Pas en C++, c'est vrai... mais tout de même bien dégueu. A ce que je peux lire sur dvp, mon cas ne semble pas isolé, et même dans mes loisirs ça me poursuit.
    J'ai même des liens pour ces derniers (je sais, c'est vache, d'autant que le binaire marche pas mal et a un potentiel sympa à mon avis):
    flare:Avatar.h
    flare:Avatar.cpp
    flare: Enemy.h
    flare:Enemy.cpp

    En les regardant juste un peu (l'auteur n'aime pas quand il y a trop d'ingénierie "over-engineered" et a réagi assez étrangement quand je lui ai dis que le code dupliqué pouvait être fusionné... Bref.) tu noteras que ce n'est pas si évident, la responsabilité des pointeurs.
    Certes, je ne qualifierais jamais ce code d'exemplaire (j'ai un niveau plutôt moyen, mais la, pour voir un diamant, il faut se lever tôt) mais malheureusement, j'ai pu voir le même genre de choses en dehors de mes loisirs. Donc, ne rien avoir pour savoir à quoi exactement on à affaire... non, ce n'est pas un simple manque, et la ou je considérait les opérateurs de flux comme juste pas beaux et ne méritant pas de débat la-dessus, unique_ptr n'est pas fini a mon sens.
    Pour string, on a "c_str()" pour la rétro-compatibilité. Pour unique_ptr, on aurait très bien pu avoir "get()" pour la compat, et un opérateur de cast pour les situations propres, c'est à dire les nouveaux codes.

    Citation Envoyé par Flob90 Voir le message
    Je ne vois pas en quoi l'abstraction est meilleurs, je n'ai pas dit de ne pas réaliser d'interface permettant une abstraction des bibliothèques utilisés en implémentation, mais de laisser le rôle de la gestion de la durée de vie à la capsule, c'est ce que la capsule encapsule qui doit offrir l'abstraction des détails d'implémentation. Dans les deux cas l'abstraction est la même, la différence est que dans un cas les rôles sont clairs, une classes un rôle (unique_ptr : gestion de la durée de vie, ta classe : abstraction de l'implémentation, rôle précis définie selon le contexte), dans l'autre ta classe fait deux choses.
    Tu veux dire que pour toi, il faudrait faire les 2? C'est a dire, une classe qui encapsule les fonctions qui nous gênent, et que cette classe utilise std::unique_ptr?
    J'aime beaucoup quand mes classes ne gèrent qu'une seule chose, mais la, tu pousses le bouchon un peu loin Maurice! (désolé, pas pu me retenir pour la blague )

    Citation Envoyé par Flob90 Voir le message
    Le paragraphe que j'ai fait n'est surement pas clair
    J'ai fait bien pire
    Citation Envoyé par Flob90 Voir le message
    , mais pour faire simple, une référence n'a aucune notion d'espace mémoire (contrairement à un pointeur) dans la description du C++, ce n'est pas une entité physique en mémoire. Or l'objectif des conteneurs est de "contenir" des entités physique en mémoire (des objets), donc par définition pas des références.
    Je vois ce que tu veux dire.
    D'un autre côté, le pointeur en lui-même n'a pas uniquement la vocation de conteneur, même s'il en est un.
    L'intérêt majeur du pointeur contre la référence, pour moi, c'est l'arithmétique: on peut avoir un pointeur nul, contrairement aux références. On peut incrémenter un pointeur sans savoir ou il va pointer, ce n'est "pas un problème" dans certains cas (entrées/sorties de puces, mémoire vidéo à l'époque du mode réel, par exemple). Cependant, il me semble que dans la plupart des cas, le besoin d'allouer manuellement à été supprimé. De la même façon qu'on ne fait plus de malloc/calloc/realloc/free, et que l'on considère ça comme un bien, il est regrettable de toujours devoir jouer du new/delete pour avoir du polymorphisme.
    D'autant que la référence à un avantage de grande taille: elle ne peux être nulle.

  11. #11
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Tu penses à quelque-chose comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    struct Ressource1 {};
    struct Ressource2 {};
     
    class MyClass
    {
      Ressource1* r1;
      Ressource2* r2;
     
    public:
      MyClass() :
        r1(new Ressource1()),
        r2(new Ressource2())
      {}
      ~MyClass()
      { delete r1; delete r2; }
    };
    Le problème apparaît lorsqu'une allocation réussi et la seconde non (exception), on a (au mieux) une fuite mémoire. Bien entendu tu peux "bidouiller" un truc dans le corps du constructeur pour corriger ça, ça marche, mais ça sera peu modulaire alors qu'un std::unique_ptr règle le problème de manière élégante.

    Pour la suite, je n'avais pas compris ton message comme ca, en effet ca rallonge ton code. Cependant c'est paradoxal que tu reproches ceci :
    • D'un côté tu dis que cette verbosité de la move semantic te dérange
    • De l'autre tu dis que l'absence de verbosité introduite par le "manque" d'un meilleur élément que T* dans le coupe unique_ptr/T* te dérange

    Alors que la verbosité de la move-semantic joue un rôle similaire à un no_own_ptr :
    • La verbosité de la move semantic permet de savoir que la responsabilité est transféré
    • La verbosité d'un no_own_ptr permet de savoir que le pointeur n'a aucune reponsabilité


    Pour le reste, je pense qu'on est d'accord sur le fait qu'il manque surement (je préfère rester prudent : on est jamais à l'abris de difficulté qu'il y aurait à faire un tel système) quelque chose à std::unique_ptr. Cependant quand quelque chose est imparfait (dans le sens non complet), tu préfères t'en passer totalement que d'utiliser la partie qui fonctionne ? La partie des mécanismes de std::unique_ptr concernant la prise de responsabilité et le transfert est fonctionnelle, il manque "juste" la partie concernant le "référencement additionnel" (j'ai pas trouvé mieux comme formulation). Et encore, cette partie n'est pas absente, juste discutable.

  12. #12
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Effectivement, j'avais oublié cette notion d'utiliser les exceptions si une allocation foire (pourtant j'avais lu quelques articles la dessus qui étaient des plus pertinents ).
    D'ailleurs, comment ça se passerai si le problème avait lieu entre le new et le constructeur d'unique_ptr?
    Il me semble que c'est pour ça que shared_ptr à un mécanisme pour les incorporer: make_shared. J'imagine que s'ils ont pris la peine de faire cet outil, il y a une raison. J'ai du mal à comprendre pourquoi ils n'ont pas utilisé la même logique de construction pour les shared_ptr et unique_ptr. Certes, ces templates ont des rôles distincts, mais je trouve que ça embrouille l'esprit?

    Pour ce qui est de la verbosité, ce que je reproche réellement, c'est le fait d'avoir une verbosité bâtarde: d'un côté on a du code dont la longueur va être rallongée, que l'on peut certes raccourcir à grand coup de typedef et #define, mais d'un autre côté on a un no_own_ptr qui est lui réduit à son plus simple appareil: un pointeur nu, ce qui fait qu'un arrivant ne saura pas s'il s'agit d'un vrai pointeur nu ou pas.
    C'est plus la différence de verbosité qui me gêne. Bon, après, honnêtement, taper 5 caractères de plus n'est pas si gênant que ça, quand ce n'est fait qu'une ou deux fois.

    Et pour le fait de m'en passer complètement, du coup, mon code m'embrouille moins (l'habitude? Certaines comme les printf/scanf sont dures à perdre, les pointeurs sont peut-être du même genre). J'ai des erreurs nettement plus lisibles (je n'ai pas encore testé tes outils), je sais comment gérer mes pointeurs et je connais le comportement précis lorsque je transfère une responsabilité.
    Certes, un arrivant sera tout aussi perdu que si j'avais gardé les unique pour le coup. Sauf que je pense que les dev C++ sont un minimum habitués à jouer avec les pointeurs. (Bon, ok, je ne gère pas toutes les exceptions du coup, si un push_back sur un vector<Truc*> foire, j'aurai peut-être un crash.)
    A côté de ça, utiliser d'autres bibliothèques, telles que boost::serialization (qui était à l'origine de ce sujet quand même) ou wxWidgets va être nettement moins pénible d'un autre côté mon code utilisant wxWidgets était déjà avec des pointeurs nus. Je pense qu'une fois que j'en aurai fini avec le gros de ce dev, je chercherais une meilleure alternative à wxWidgets d'ailleurs... Je me demande s'il existe une lib graphique écrite qui ne réinvente pas la roue comme les string et les conteneurs? - Déjà, pas Qt ni wxWidgets. Peut-être Gtk?)

    Au final, j'ai envie de dire que le jour ou j'écris du code que je n'ai pas besoin d'interfacer avec de l'ancien, il est probable que j'utilise les avantages de unique_ptr, en y réfléchissant à deux fois à chaque fois que je commence à l'intégrer à un attribut de mon code. (Peut-être reprendre la bibliothèque générique de gestion de fenêtres que j'ai commencé il y a quelques années et qui était fonctionnelle? La refaire avec l'expérience que j'ai acquise pourrait être fun )
    Par contre, quand je vois les commentaires et documents qui conseillent l'usage de ces pointeurs aux débutants, sans jamais parler de la move semantic, ça me fait plus peur qu'autre chose. Dans l'état actuel des choses, je ne le leur conseillerais pas, perso.
    Ces unique_ptr sont loin d'éviter tous les crash, et l'affirmer est aussi stupide que de dire qu'en JAVA on peut se permettre de ne pas comprendre comment elle marche. J'ai vu eclipse planter et buguer plus d'une fois a cause d'une mauvaise gestion mémoire.

    Ils améliorent un peu la sécurité, dans le cas des initialisations de plusieurs pointeurs dans un seul constructeur, c'est vrai. Mais ce cas ne m'arrive que rarement (la plupart du temps, comme je l'ai dis, je n'ai qu'une seule agrégation, les autres pointeurs sont des associations, et il n'y a donc pas de relation de responsabilité) et ils n'empêchent pas tous les problèmes, tout en rendant la compilation plus pénible (plus lente aussi, les templates ne sont pas réputés pour leur vitesse, et compiler sur un netbook dans un train non plus)

  13. #13
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Freem Voir le message
    Effectivement, j'avais oublié cette notion d'utiliser les exceptions si une allocation foire (pourtant j'avais lu quelques articles la dessus qui étaient des plus pertinents ).
    C'est pourtant le but premier d'une telle classe.

    Citation Envoyé par Freem Voir le message
    D'ailleurs, comment ça se passerai si le problème avait lieu entre le new et le constructeur d'unique_ptr?
    Il me semble que c'est pour ça que shared_ptr à un mécanisme pour les incorporer: make_shared. J'imagine que s'ils ont pris la peine de faire cet outil, il y a une raison. J'ai du mal à comprendre pourquoi ils n'ont pas utilisé la même logique de construction pour les shared_ptr et unique_ptr. Certes, ces templates ont des rôles distincts, mais je trouve que ça embrouille l'esprit?
    Il manque en effet un make_unique, mais ce n'est pas dans ce cas là qu'il se révèle le plus utile. L'existence de make_shared a trois objectifs :
    • Avoir un moyen de créer un shared_ptr sans avoir à écrire de new (besoin purement stylistique).
    • Augmenter les performances en allouant simultanément l'espace pour l'objet et le compteur.
    • Augmenter l'exception-safety dans certains cas (création d'un shared_ptr en paramètre de fonction directement, typiquement).

    Dans boost l'idée de std::unique_ptr s'appel boost::scoped_ptr (cf PS), bien entendu la move-semantic en moins (ca date d'avant le C++11). Le but de cette idée étant de répondre au problème mentionné plus haut, le besoin d'un make_scoped (make_unique) était moins trivial :
    • Le besoin premier étant l'utilisation pour l'initialisation de données membres, le besoin stylistique est moins évident (autant make_shared<T>(); est "mieux" (*) que shared_ptr<T>(new T()); autant p(make_unique<T>()) n'est pas si "différent" (*) de p(new T()); ).
    • Pas de compteur, la question ne se pose pas a priori.
    • A nouveau le besoin premier n'allait pas dans l'idée d'écriture nécessitant un tel degré d'exception-safety.

    Bien entendu la mise en place de la move-semantic permet à std::unique_ptr d'avoir de nouveaux cas d'utilisation remettant en cause le dernier points.

    Citation Envoyé par Freem Voir le message
    Pour ce qui est de la verbosité, ce que je reproche réellement, c'est le fait d'avoir une verbosité bâtarde: d'un côté on a du code dont la longueur va être rallongée, que l'on peut certes raccourcir à grand coup de typedef et #define, mais d'un autre côté on a un no_own_ptr qui est lui réduit à son plus simple appareil: un pointeur nu, ce qui fait qu'un arrivant ne saura pas s'il s'agit d'un vrai pointeur nu ou pas.
    C'est plus la différence de verbosité qui me gêne. Bon, après, honnêtement, taper 5 caractères de plus n'est pas si gênant que ça, quand ce n'est fait qu'une ou deux fois.
    Dans ce cas là je suis d'accord, cependant le C++ est un langage qui évolue encore, mais ce n'est pas en utilisant pas les outils qui existent déjà en disant qu'on peut faire mieux (**) que ça va se faire. Mais plutôt en les utilisant permettant ainsi de les améliorer dans le future. Si la communauté n'utilise pas std::unique_ptr (comme tu en as pris la décision), c'est pas vraiment un encouragement pour l'améliorer et proposer un std::no_own_ptr et std::make_unique !

    Citation Envoyé par Freem Voir le message
    Par contre, quand je vois les commentaires et documents qui conseillent l'usage de ces pointeurs aux débutants, sans jamais parler de la move semantic, ça me fait plus peur qu'autre chose. Dans l'état actuel des choses, je ne le leur conseillerais pas, perso.
    Ces unique_ptr sont loin d'éviter tous les crash, et l'affirmer est aussi stupide que de dire qu'en JAVA on peut se permettre de ne pas comprendre comment elle marche. J'ai vu eclipse planter et buguer plus d'une fois a cause d'une mauvaise gestion mémoire.
    J'ai l'impression que tu accordes plus d'importance à la move semantic pour std::unique_ptr qu'elle n'en a. L'on conseille aux nouveaux d'utiliser std::unique_ptr dans les cas d'utilisation typique de l'exemple que j'ai mentionné. Dans cette situation, il est bien plus simple de dire à un nouveau que std::unique_ptr va faire le boulot que de chercher à lui expliquer pourquoi c'est nécessaire.

    Citation Envoyé par Freem Voir le message
    Ils améliorent un peu la sécurité, dans le cas des initialisations de plusieurs pointeurs dans un seul constructeur, c'est vrai. Mais ce cas ne m'arrive que rarement (la plupart du temps, comme je l'ai dis, je n'ai qu'une seule agrégation, les autres pointeurs sont des associations, et il n'y a donc pas de relation de responsabilité) et ils n'empêchent pas tous les problèmes, tout en rendant la compilation plus pénible (plus lente aussi, les templates ne sont pas réputés pour leur vitesse, et compiler sur un netbook dans un train non plus)
    Le cas que tu considères comme rare est le cas d'utilisation typique de cette capsule : c'est sa raison d'être. Ce sont les autres cas d'utilisation qui devraient être atypique (***) (transfert de responsabilité). Pour les autres associations, std::unique_ptr ou pas, à l'heure actuelle ça sera de toute façon des pointeurs (****).

    A la réflexion, j'ai vraiment l'impression que ton problème/ressenti vient du fait que tu les utilises dans un contexte où tu as besoin d'un transfert de responsabilité utilisant des mécanismes nouveaux (move semantic). Les mécanismes sont en place, mais les idiomes d'utilisation pas nécessairement, lorsqu'ils [les idiomes] seront bien définis, il est plus probable qu'une telle utilisation te semble plus aisée.

    (*) Plus court pour une verbosité identique, AMA.
    (**) J'insiste bien sur le "mieux", bien entendu si l'outil proposé était inutilisable les choses serait différentes. Mais à l'heure actuelle std::unique_ptr répond très bien à l'objectif premier comme le faisait boost::scoped_ptr. C'est la mise en place de la move semantic et des nouvelles voix qu'elle ouvre qui le rende imparfait.
    (***) Du moins à l'heure actuelle.
    (****) J'exclut le cas de la référence ici, ce n'est pas la discussion.

    PS: J'exagère un peu, std::auto_ptr qui contenait cette idée de transfert de responsabilité existait aussi, et est plus proche de std::unique_ptr que boost::scoped_ptr. Cependant l'idée de transfert mise en place par auto_ptr (en l'absence de move semantic) pour le transfert était assez bancal, et au final le principal intérêt d'auto_ptr était son caractère RAII, ce qui revenait donc à boost::scoped_ptr, l’existence d'un transfert de responsabilité bancal inexistant. Donc même si std::unique_ptr est "une correction" de std::auto_ptr, j'ai quand même l'impression qu'on ressent le "passé" de boost::scoped_ptr dans std::unique_ptr (ce qui expliquerait qu'il manque des choses : ces choses qui n'avaient pas de raisons d'être pour boost::scoped_ptr mais qui peuvent en avoir avec l'introduction de la move semantic et des cas d'utilisations qui vont en découler).

  14. #14
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Effectivement, vu ce que tu m'expliques, je n'avais pas compris son rôle premier. Pour le coup, si on a un outil qui n'est bon que dans le cas de l'initialisation de plusieurs pointeurs par le constructeur, quelles sont les différences entre unique_ptr et auto_ptr?

    Il m'a semblé comprendre que le 2nd était justement décrié par rapport à ses problèmes de transmission de responsabilité, ce qui aurait engendré la naissance de unique_ptr. Donc, ce dernier doit corriger quelque chose, mais quoi?

    Par rapport à l'évolution du C++, je sais bien que ça évolue, et je pense même que le dernier standard a apporté un certain nombre d'améliorations qui vont nous simplifier la vie:
    _ nouvelle syntaxe du for (ça n'a l'air de rien, mais, ouah, ça deviens tout de suite plus agréable de parcourir les collections. En fait, l'ancienne syntaxe va sûrement vite devenir marginale)
    _ lambda (déjà joué un tout petit peu avec, et les transform et for_each vont sûrement être plus populaires. Surtout transform d'ailleurs. Plus besoin de faire un foncteur pour faire m_i++, c'est agréable)
    _ multithread (je n'ai pas encore eu l'occasion de pratiquer ça, par contre, mais je sais qu'un jour pas trop lointain je jouerais avec)
    _ ...

    Quand j'ai lu pour shared_ptr et unique_ptr, j'ai trouvé que c'étaient de bonnes choses.
    Un peu de lecture plus tard, j'ai réfléchi à nouveau sur le shared, et j'admets qu'il doit pouvoir servir, mais je ne vois plus dans quels cas (mes classes sont toujours égoïstes: elles ne partagent jamais ces radines ). Maintenant, j'ai testé le unique (sûrement pas dans le bon cas cela dis) et je le trouve incomplet.
    Enfin, il faut quand même que je précise que je me suis éclaté avec les spec de mon appli: logiciel de dessin vectoriel qui n'a aucune fonctionnalité par lui-même, vu que les fonctionnalités sont apportées par des plugin (pour éviter de faire un code non réutilisable comme d'autres qui ont voulu refaire le même logiciel on fait) avec tout le dynamisme que cela impose (pas les même fonctionnalités d'une instance à l'autre, notamment)
    A partir de la, j'avais deux choix pour créer une forme:
    _ soit le plugin crée et modifie directement la forme dans la liste de celles qui sont rendues, donc pas de transfert de responsabilité.
    _ soit le plugin gère toute la création et déplace ensuite la responsabilité.

    J'étais partis sur la 1ère solution avant de basculer à la seconde: moins de communication et moins d'appels "vides" en chaîne me semblait une meilleure solution (niveau perf comme niveau modèle). Il y a aussi le problème que si le plugin plante, il supprimera juste ce sur quoi il travaillait, au lieu de tout casser.
    Accessoirement, ça pourra aussi me permettre certaines optimisation (quand j'en serai arrivé la).

    Vu que j'ai une situation ou les transferts de responsabilités sont assez fréquents, unique_ptr n'est pas, ou ne me semble plus, approprié à mon problème. Mon ras-le-bol serait donc en partie dû à une mauvaise utilisation. V'la t'y pas que je commence à blâmer les outils parce que je sais pas à quoi ils servent

    Tiens, d'ailleurs, je viens de me poser une question.
    J'ai l'impression que le standard utilise peu la surcharge des opérateurs de cast (notamment quand on parle de pointeurs):
    std::string =>c_str()
    std::unique_ptr => get()
    Y a t-il une raison?
    Parce que du coup, il aurait été possible de faire un opérateur de cast pour unique_ptr qui aurait pu pas mal simplifier la vie:
    "operator T * const ()" du fait du const, on sait qu'on ne doit pas modifier le pointeur (et il me semble que le compilo vérifie, mais j'utilise rarement des pointeurs constants donc...), mais on a du coup un pointeur, qui permet de modifier la donnée pointée.
    J'ai l'impression que ça aurait été plus pertinent que de forcer à utiliser get() dès lors que l'on a besoin d'accéder, non pas au pointeur, mais à ses données avec la syntaxe pointeur, chose qui est la plus fréquente, non?
    En fait, on aurait eu un comportement plus proche de celui d'un véritable pointeur, tout simplement, et j'ai toujours vu les pointeurs intelligents comme des pointeurs à la syntaxe proche des bruts, mais à la sémantique plus évoluée. Ici, la sémantique est effectivement plus évoluée, mais la syntaxe pourrait être rendue plus proche...

    Peut-être que c'est du au fait que la constance est méchamment bordélique pour les pointeurs, mais j'imagine que ceux qui vont au comité sont des gurus et ne doivent pas ignorer comment ça semble marcher (semble, parce que pas testé) .
    Bon, c'est vrai aussi qu'il est rare de voir des const autour des pointeurs dans les libs, tout comme il est vrai qu'on peut utiliser le const_cast... mais il y a un moment ou l'on n'y peut rien si leurs auteurs n'ont pas un minimum de bonnes pratiques (d'autant que const à toujours existé, donc pas d'excuse de portabilité là).

    [edit]
    Au final, cette discussion à tellement dérivé que je me demande si il ne serait pas pertinent de changer le titre xD
    [/edit]

  15. #15
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Freem Voir le message
    Effectivement, vu ce que tu m'expliques, je n'avais pas compris son rôle premier. Pour le coup, si on a un outil qui n'est bon que dans le cas de l'initialisation de plusieurs pointeurs par le constructeur, quelles sont les différences entre unique_ptr et auto_ptr?

    Il m'a semblé comprendre que le 2nd était justement décrié par rapport à ses problèmes de transmission de responsabilité, ce qui aurait engendré la naissance de unique_ptr. Donc, ce dernier doit corriger quelque chose, mais quoi?
    Oui std::unique_ptr corrige le problème de auto_ptr, après il reste imparfait, surment du au fait que auto_ptr étant imparfait, les problèmes et utilisation typique de l'idée de transfert de responsabilité sont moins maîtrisés que la partie "responsabilité unique" (difficile d'expérimenter et améliorer quelque chose quand les mécanismes ne le permettent pas). Du coup on se retrouve avec un std::unique_ptr qui fait très bien une partie du travail (celle de boost::scoped_ptr) qui fait mieux la partie transfert de responsabilité que std::auto_ptr, mais qui reste améliorable sur certains point (*).

    Pour le problème de std::auto_ptr, l'absence de move semantic fait que le constructeur de copie et l'operateur d'affectation étaient utilisé pour faire ce boulot, ça demandait une certaine (trop grande et hasardeuse) prudence pour utiliser le transfert de responsabilité. La move semantic corrige se problème en introduisant les outils qu'il manquait : move constructor et move assign.

    Citation Envoyé par Freem Voir le message
    Quand j'ai lu pour shared_ptr et unique_ptr, j'ai trouvé que c'étaient de bonnes choses.
    Un peu de lecture plus tard, j'ai réfléchi à nouveau sur le shared, et j'admets qu'il doit pouvoir servir, mais je ne vois plus dans quels cas (mes classes sont toujours égoïstes: elles ne partagent jamais ces radines ). Maintenant, j'ai testé le unique (sûrement pas dans le bon cas cela dis) et je le trouve incomplet.
    shared_ptr n'est pas nécessaire à tout design, mais on peut toujours en avoir besoin. Et pour le coup shared_ptr est assez bien connue et maitrisé, le C++11 n'apporte rien de fondamentale (comme mécanisme) pour lui. Au final le C++11 ne fait "que" introduire un élément (pour std::shared_ptr) qui existe et fonctionne bien depuis plusieurs année, c'est pas le cas de std::unique_ptr.

    Citation Envoyé par Freem Voir le message
    Enfin, il faut quand même que je précise que je me suis éclaté avec les spec de mon appli: logiciel de dessin vectoriel qui n'a aucune fonctionnalité par lui-même, vu que les fonctionnalités sont apportées par des plugin (pour éviter de faire un code non réutilisable comme d'autres qui ont voulu refaire le même logiciel on fait) avec tout le dynamisme que cela impose (pas les même fonctionnalités d'une instance à l'autre, notamment)
    A partir de la, j'avais deux choix pour créer une forme:
    _ soit le plugin crée et modifie directement la forme dans la liste de celles qui sont rendues, donc pas de transfert de responsabilité.
    _ soit le plugin gère toute la création et déplace ensuite la responsabilité.

    J'étais partis sur la 1ère solution avant de basculer à la seconde: moins de communication et moins d'appels "vides" en chaîne me semblait une meilleure solution (niveau perf comme niveau modèle). Il y a aussi le problème que si le plugin plante, il supprimera juste ce sur quoi il travaillait, au lieu de tout casser.
    Accessoirement, ça pourra aussi me permettre certaines optimisation (quand j'en serai arrivé la).

    Vu que j'ai une situation ou les transferts de responsabilités sont assez fréquents, unique_ptr n'est pas, ou ne me semble plus, approprié à mon problème. Mon ras-le-bol serait donc en partie dû à une mauvaise utilisation. V'la t'y pas que je commence à blâmer les outils parce que je sais pas à quoi ils servent
    Ça rejoins ce que je disais dans mon message précédent, plus que le fait que l'outil soit incomplet (il reste utilisable), c'est peut-être l’absence d'utilisation typique bien définie à l'heure actuelle qui donne cette impression de "brouillon". Avec le temps, la communauté devrait affiner la façon d'utiliser ces outils et l'utilisation deviendra plus "propre".

    Citation Envoyé par Freem Voir le message
    Tiens, d'ailleurs, je viens de me poser une question.
    J'ai l'impression que le standard utilise peu la surcharge des opérateurs de cast (notamment quand on parle de pointeurs):
    std::string =>c_str()
    std::unique_ptr => get()
    Y a t-il une raison?
    Parce que du coup, il aurait été possible de faire un opérateur de cast pour unique_ptr qui aurait pu pas mal simplifier la vie:
    "operator T * const ()" du fait du const, on sait qu'on ne doit pas modifier le pointeur (et il me semble que le compilo vérifie, mais j'utilise rarement des pointeurs constants donc...), mais on a du coup un pointeur, qui permet de modifier la donnée pointée.
    J'ai l'impression que ça aurait été plus pertinent que de forcer à utiliser get() dès lors que l'on a besoin d'accéder, non pas au pointeur, mais à ses données avec la syntaxe pointeur, chose qui est la plus fréquente, non?
    En fait, on aurait eu un comportement plus proche de celui d'un véritable pointeur, tout simplement, et j'ai toujours vu les pointeurs intelligents comme des pointeurs à la syntaxe proche des bruts, mais à la sémantique plus évoluée. Ici, la sémantique est effectivement plus évoluée, mais la syntaxe pourrait être rendue plus proche...
    La sémantique des pointeurs intelligents est rendu identique à celle des pointeurs par la mise à disposition des fonctions membre operator* et operator->, des opérateurs de comparaisons (libre) et de operator[] dans certains cas (les opérateurs arithmétique n'auraient pas vraiment de sens (**) avec la notion de responsabilité).

    Le but premier de get (on le retrouve pour tout les pointeurs intelligents) c'est la rétro-compatibilité, alors bien entendu ton idée fonctionne, mais niveau verbosité on y perd :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    foo_c(my_unique.get());
    foo_c((T*)my_unique);
    foo_c(static_cast<T*>(my_unique));
    L'appréciation d'un code reste subjective, mais je trouve que le premier est quand même plus clair : on récupère (get) le pointeur d'un pointeur intelligent pour une fonction C. Alors que les deux autres ...

    Dans le cas de std::unique_ptr get sert aussi à construire le second élément du couple std::unique_ptr/T* (à comparer à std::shared_ptr/std::weak_ptr), c'est là qu'il manque peut-être quelque chose comme l'a souligné Loic. Cependant si une capsule no_own_ptr venait à exister, il est plus probable que sa construction se fasse de manière non intruisive comme le fait std::weak_ptr avec std::shared_ptr.

    (*) Le C++11 apportant les mécanismes techniques, c'est la partie design qui pourra être améliorer dans le future.
    (**) Pas de manière intuitive du moins.

  16. #16
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    A propos de l'utilisation type de unique_ptr, même si je manque de pratique (j'ai dû jusqu'à présent bosser sur des compilateurs ne le permettant pas...) j'ai souvent regretté de ne pas pouvoir l'utiliser, et ai utilisé un shared_ptr à regret pour le remplacer.

    Mon exemple typique est l'objet que je suis obligé de manipuler par pointeur (parce que je veux du polymorphisme, par exemple) mais pour lequel il n'y a qu'un seul possesseur logique, et que je ne veux pas faire de transmission de propriété, au sens sémantique du terme (même si un transfert pour des raisons bassement techniques peut arriver).

    Exemple pour clarifier : Un Drawing possède des Shape qui sont en faite des Circle, Rectangle... Et bien je donnerais à drawing une donnée membre de type vector<unique_ptr<Shape>>. Il y aura certes des transferts d'unique_ptr lors de la réallocation de vector, mais d'un point de vu design, la propriété des shape est unique, et associée une bonne fois pour toute au dessin.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  17. #17
    Membre émérite
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Décembre 2008
    Messages
    832
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Décembre 2008
    Messages : 832
    Points : 2 625
    Points
    2 625
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    shared_ptr n'est pas nécessaire à tout design, mais on peut toujours en avoir besoin. Et pour le coup shared_ptr est assez bien connue et maitrisé, le C++11 n'apporte rien de fondamentale (comme mécanisme) pour lui. Au final le C++11 ne fait "que" introduire un élément (pour std::shared_ptr) qui existe et fonctionne bien depuis plusieurs année, c'est pas le cas de std::unique_ptr.
    Oui, quand on voit la bête, on sens que ça peut arriver, comme je l'ai dis, je pressens qu'il peut servir. Mais je ne vois pas trop où

    Citation Envoyé par Flob90 Voir le message
    Ça rejoins ce que je disais dans mon message précédent, plus que le fait que l'outil soit incomplet (il reste utilisable), c'est peut-être l’absence d'utilisation typique bien définie à l'heure actuelle qui donne cette impression de "brouillon". Avec le temps, la communauté devrait affiner la façon d'utiliser ces outils et l'utilisation deviendra plus "propre".
    Oui.

    Citation Envoyé par Flob90 Voir le message
    La sémantique des pointeurs intelligents est rendu identique à celle des pointeurs par la mise à disposition des fonctions membre operator* et operator->, des opérateurs de comparaisons (libre) et de operator[] dans certains cas (les opérateurs arithmétique n'auraient pas vraiment de sens (**) avec la notion de responsabilité).

    Le but premier de get (on le retrouve pour tout les pointeurs intelligents) c'est la rétro-compatibilité, alors bien entendu ton idée fonctionne, mais niveau verbosité on y perd :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    foo_c(my_unique.get());
    foo_c((T*)my_unique);
    foo_c(static_cast<T*>(my_unique));
    L'appréciation d'un code reste subjective, mais je trouve que le premier est quand même plus clair : on récupère (get) le pointeur d'un pointeur intelligent pour une fonction C. Alors que les deux autres ...
    Et ceci?
    Ce code est totalement fonctionnel si l'on a, dans la déclaration de unique_ptr, un opérateur de cast. Et si l'on prend la "peine" (pour éviter une bêtise du dev) de ne faire que l'opérateur de cast vers un pointeur constant qui permet la modification du contenu pointé, il n'y a pas de risque de perdre la responsabilité, contrairement à get() (puisque le pointeur est constant, sans que ce vers quoi il pointe le sois).
    La différence? On garderais get(), mais quand l'utilisateur l'utiliserais, il saurait qu'il vaudrait mieux vérifier que la mémoire n'a pas été altérée.

    Et pas besoin du static_cast quand l'opérateur est implémenté. Ecrire static_cast dans cette situation, c'est exactement comme d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    char *c_style_power;
    std::string cpp_revenge(static_cast<std::string>(c_style_power));
    Pour reprendre ton code, il permet de briser la raison même d'être de la RAII:
    quand tu donnes ton pointeur T*, tu autorises explicitement (pas de const, après tout) l'utilisateur à en faire ce qu'il veut. Il peut le delete, ou plus stupide encore, faire une jolie cochonnerie du genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    unique_ptr<int> x(new int(10));
    foo_c(x.get());
    ...
    foo_c(int *a)
    {
    ...
    a=realloc(malloc(syzeof(int)));
    ...
    }
    La, ton pointeur "intelligent" il possède une adresse mémoire qui a déjà été allouée, et en plus de ça, "a", lui, va causer un memory leak. 2 en 1, c'est jour de promo
    Naturellement, on peut estimer que les gens ne sont généralement pas bêtes à ce point, surtout en C ou en C++, quand on voit des pointeurs, on sait qu'on fait mumuse avec un truc qui peut nous péter à la tronche.
    Mais il se pourrait aussi que cette fonction foo_c() en réalité très bien conçue pour l'ère du C98 ne soit pas sécurisée en cas de multithread. Ou que le jeu des pointeurs l'invalide pour une raison x ou y.
    Si je prend le cas de la SDL, librairie graphique assez connue, il y a une fonction, SDL_BlitSurface qui dans un cas précis, perd le contrôle de sa zone mémoire (avec DirectX 5... mais ce n'est pas l'important).

    Accessoirement, écrire "monPointeur.get()" me rappelle un truc qui m'a fait vomir en C# quand je l'ai vu en BTS (ça remonte alors pardonne l'erreur de mot-clé) ==> IntPtr.zero !!!
    Franchement, c'est-y pas mignon ça? On doit vraiment se retrouver avec ce type de choses en C++?

    Citation Envoyé par Flob90 Voir le message
    Dans le cas de std::unique_ptr get sert aussi à construire le second élément du couple std::unique_ptr/T* (à comparer à std::shared_ptr/std::weak_ptr), c'est là qu'il manque peut-être quelque chose comme l'a souligné Loic.
    Comme je l'ai dis, get() renvoie un pointeur nu, brut, libre de toute contrainte. C'est ca, le problème de get, c'est ça qui fait qu'il brise la RAII, en obligeant de contourner la sécurité de unique_ptr en quasi-permanence, juste pour pouvoir avoir une association qui pointe sur la même donnée, la ou un pointeur constant suffirait.
    Si on pouvait se limiter à get() pour des raisons de compatibilité avec du vieux code... Quoique, en fait, même pas.
    Quand on arrive sur du vieux code, celui-ci peut très bien utiliser des pointeurs constants, ça existait.
    Et quand ce vieux code à le besoin de la responsabilité de l'objet, unique_ptr à la fonction release() qui permet de libérer un pointeur de l'emprise d'un unique_ptr.

    Donc, face à du code de compatibilité, il faudrait utiliser ceci, pour être propre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    unique_ptr<int> ptri(new int(10));
    ...
    int*cptri=ptri.release();
    foo_c(cptri);
    ptri.reset(cptri);
    ...
    La, on paye le prix de la compatibilité: 3 lignes au lieu d'une. Mais au moins, si la fonction C change notre pointeur, on ne se retrouve pas avec un pointeur stupidement désynchronisé.

    Citation Envoyé par Flob90 Voir le message
    (**) Pas de manière intuitive du moins.
    Pour ce qui est de l'arithmétique sur des pointeurs à responsabilité... j'allais te rejoindre et enfoncer le clou, mais en fait, c'est faux.
    On utilise rarement un pointeur sur de la mémoire que l'on a pas allouée précédemment, pas vrai?
    Dans ces situations, on a une sorte de tableau de pointeurs, et un pointeur qui pointe sur un seul de ces éléments, typiquement ce type de construction (en C):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    int main(int argc, char **argv)
    {
    int *memory=malloc(typeof(int)*100);
    int *integer=memory;
    integer++;
    free(memory);
    }
    (ouah... obligé d'aller voir le prototype de malloc pour me rappeler comment il marche j'ai failli le confondre avec celui de write)
    Si on devait avoir de l'arithmétique sur des pointeurs unique, je ne vois aucunement le souci.
    Imagines le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int main(int argc, char **argv)
    {
    unique_ptr<int> memory(new int[100]);
    no_own_ptr<int> integer=memory;
    integer++;
    }
    Évidemment, on ne fait pas l'arithmétique directement sur le pointeur responsable, mais si l'on avait une capsule no_own, comme tu l'appelles, ce code ne poserait aucun souci.

    Pour ce qui est du transfert de responsabilité... je ne sais pas trop comment améliorer la chose. Probablement parce que je n'ai pas compris toutes les ficelles de la move semantic.
    Les solutions que j'ai utilisées jusque la, ça a été:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    //1ère
    myVector.push_back(std::move(myPtr));
    //2nde
    myVector.push_back(std::unique_ptr<Truc>());
    myVector.back().swap(myPtr);
    selon la situation... sauf que je me suis retrouvé avec un crash lorsque je quitte l'appli (crash qui n'est pas résolu avec les pointeurs nus - logique - , mais vu le moment du crash il ne me gêne pas encore trop).
    Et comme j'ai eu le fréquent besoin d'avoir des associations entre différentes classes, sans qu'il n'y ait de réelle hiérarchie, la, c'est devenu assez folklorique.

    Et j'ai un mal fou à pardonner le fait que get() brise l'encapsulation et permettre de faire manipuler l'adresse mémoire par quelqu'un qui n'en est pas responsable, alors qu'il existe release() qui permet de le faire "temporairement". (ok, cette ligne est laide, mais elle marcherait et serait plus sûre que le coup du get(): "truc.reset(realloc(truc.get(),taille,sizeof(int));")
    En fait, je trouve même étrange qu'il n'y ait pas un mécanisme pour faciliter ce type de transaction.

    Citation Envoyé par JolyLoic Voir le message
    A propos de l'utilisation type de unique_ptr, même si je manque de pratique (j'ai dû jusqu'à présent bosser sur des compilateurs ne le permettant pas...) j'ai souvent regretté de ne pas pouvoir l'utiliser, et ai utilisé un shared_ptr à regret pour le remplacer.

    Mon exemple typique est l'objet que je suis obligé de manipuler par pointeur (parce que je veux du polymorphisme, par exemple) mais pour lequel il n'y a qu'un seul possesseur logique, et que je ne veux pas faire de transmission de propriété, au sens sémantique du terme (même si un transfert pour des raisons bassement techniques peut arriver).

    Exemple pour clarifier : Un Drawing possède des Shape qui sont en faite des Circle, Rectangle... Et bien je donnerais à drawing une donnée membre de type vector<unique_ptr<Shape>>. Il y aura certes des transferts d'unique_ptr lors de la réallocation de vector, mais d'un point de vu design, la propriété des shape est unique, et associée une bonne fois pour toute au dessin.
    J'ai cru comprendre que auto_ptr n'a comme seul problème celui des transmissions de responsabilité. Pourquoi ne pas l'utiliser, au lieu d'utiliser un outil qui n'est pas du tout fait pour?
    Parce que bon, shared_ptr, à côté de unique/scoped/auto, c'est long et lourd en terme de CPU et de mémoire...

  18. #18
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par Freem Voir le message

    J'ai cru comprendre que auto_ptr n'a comme seul problème celui des transmissions de responsabilité. Pourquoi ne pas l'utiliser, au lieu d'utiliser un outil qui n'est pas du tout fait pour?
    Parce que bon, shared_ptr, à côté de unique/scoped/auto, c'est long et lourd en terme de CPU et de mémoire...
    Déjà parce qu'on ne peut pas faire de vector<auto_ptr<T>>, ça ne marcherait pas... (ujne astuce fait que ça ne doit pas compiler, mais sans cette astuce, ça compilerait, mais on perdrait des données).
    Et puis surtout parce que justement, le problème du transfert de responsabilité d'auto_ptr, ce n'est pas qu'il ne s'exécute pas bien, c'est qu'il ne s'exécute pas au bon moment. Et donc si je l'utilisais, je me retrouverais avec du code très risqué où le simple fait de copier le pointeur rendrait le pointeur initial invalide... Et je ne parle pas au hasard, dans mon ancienne boîte, où ils ont voulu se mettre aux pointeurs intelligents sans bien comprendre ce qu'il y avait derrière, ils ont utilisé des auto_ptr. On a eu plusieurs bugs en clientèle qui étaient liés à cette utilisation...


    boost::scoped_ptr a corrigé ce problème d'auto_ptr, en interdisant ce transfert, mais on se retrouve trop limité (et pas de vector<scoped_ptr<T>> non plus, par exemple). Et unique_ptr a pris le problème à bras le corps, en profitant que la move semantic définissait enfin le bon endroit pour faire ce transfert de responsabilité.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  19. #19
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par Freem Voir le message
    Et ceci?
    Ce code est totalement fonctionnel si l'on a, dans la déclaration de unique_ptr, un opérateur de cast. Et si l'on prend la "peine" (pour éviter une bêtise du dev) de ne faire que l'opérateur de cast vers un pointeur constant qui permet la modification du contenu pointé, il n'y a pas de risque de perdre la responsabilité, contrairement à get() (puisque le pointeur est constant, sans que ce vers quoi il pointe le sois).
    La différence? On garderais get(), mais quand l'utilisateur l'utiliserais, il saurait qu'il vaudrait mieux vérifier que la mémoire n'a pas été altérée.

    Et pas besoin du static_cast quand l'opérateur est implémenté. Ecrire static_cast dans cette situation, c'est exactement comme d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    char *c_style_power;
    std::string cpp_revenge(static_cast<std::string>(c_style_power));
    1/ Retourner T*const ca sert absolument à rien (*). Si tu fais un get tu ne peux pas modifier vers quoi pointe le pointeur intelligent, T* const ou T*. Tu peux cependant le détruire, mais pareil avec un T* const (ça s'applique de la même manière à un opérateur de cast).

    2/ Je supposais qu'un tel opérateur serait déclaré explicite, tout simplement pour que l'utilisateur ai à l'écrire et soit bien conscient de ce qu'il fait (j'étais dans l'optique du besoin de retro-compatibilité).

    3/ Si tu proposais ca pour le couple std::unique_ptr/T*, autant avoir un no_own_ptr qui fait ça de manière non invasive. D'ailleurs, si le manque d'une capsule pour le rôle de T* te gène vraiment, tu peux toujours te la coder (c'est pas une tâche insurmontable), alors que modifier std::unique_ptr tu ne peux pas (vraiment) le faire.

    Citation Envoyé par Freem Voir le message
    Pour reprendre ton code, il permet de briser la raison même d'être de la RAII:
    quand tu donnes ton pointeur T*, tu autorises explicitement (pas de const, après tout) l'utilisateur à en faire ce qu'il veut. Il peut le delete, ou plus stupide encore, faire une jolie cochonnerie du genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    unique_ptr<int> x(new int(10));
    foo_c(x.get());
    ...
    foo_c(int *a)
    {
    ...
    a=realloc(malloc(syzeof(int)));
    ...
    }
    La, ton pointeur "intelligent" il possède une adresse mémoire qui a déjà été allouée, et en plus de ça, "a", lui, va causer un memory leak. 2 en 1, c'est jour de promo
    Naturellement, on peut estimer que les gens ne sont généralement pas bêtes à ce point, surtout en C ou en C++, quand on voit des pointeurs, on sait qu'on fait mumuse avec un truc qui peut nous péter à la tronche.
    Mais il se pourrait aussi que cette fonction foo_c() en réalité très bien conçue pour l'ère du C98 ne soit pas sécurisée en cas de multithread. Ou que le jeu des pointeurs l'invalide pour une raison x ou y.
    Si je prend le cas de la SDL, librairie graphique assez connue, il y a une fonction, SDL_BlitSurface qui dans un cas précis, perd le contrôle de sa zone mémoire (avec DirectX 5... mais ce n'est pas l'important).
    Ton const ne corrige rien à ce problème, par contre une capsule non invasive, oui.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    int* const foo()
    { return new int(); }
     
    void bar(int* i)
    { i = new int(); }
     
    int main()
    { bar(foo()); }
    Compile.

    Et ça ne créera pas de memory leak (pas de la part de unique_ptr du moins), le seul vrai risque est un double delete.

    Citation Envoyé par Freem Voir le message
    Comme je l'ai dis, get() renvoie un pointeur nu, brut, libre de toute contrainte. C'est ca, le problème de get, c'est ça qui fait qu'il brise la RAII, en obligeant de contourner la sécurité de unique_ptr en quasi-permanence, juste pour pouvoir avoir une association qui pointe sur la même donnée, la ou un pointeur constant suffirait.
    Si on pouvait se limiter à get() pour des raisons de compatibilité avec du vieux code... Quoique, en fait, même pas.
    Quand on arrive sur du vieux code, celui-ci peut très bien utiliser des pointeurs constants, ça existait.
    Et quand ce vieux code à le besoin de la responsabilité de l'objet, unique_ptr à la fonction release() qui permet de libérer un pointeur de l'emprise d'un unique_ptr.

    Donc, face à du code de compatibilité, il faudrait utiliser ceci, pour être propre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    unique_ptr<int> ptri(new int(10));
    ...
    int*cptri=ptri.release();
    foo_c(cptri);
    ptri.reset(cptri);
    ...
    La, on paye le prix de la compatibilité: 3 lignes au lieu d'une. Mais au moins, si la fonction C change notre pointeur, on ne se retrouve pas avec un pointeur stupidement désynchronisé.
    Si une fonction C veut modifier un pointeur, le paramètre sera T**, pas T*. Elle pourrait modifier la ressource par un placement new, mais dans ce cas pas de problème avec get. Si la fonction en prend la responsabilité, alors en effet il faut utiliser release. Mais si la fonction n'en prend pas la responsabilité, il n'y pas de risque avec get.

    Citation Envoyé par Freem Voir le message
    Pour ce qui est de l'arithmétique sur des pointeurs à responsabilité... j'allais te rejoindre et enfoncer le clou, mais en fait, c'est faux.
    On utilise rarement un pointeur sur de la mémoire que l'on a pas allouée précédemment, pas vrai?
    Dans ces situations, on a une sorte de tableau de pointeurs, et un pointeur qui pointe sur un seul de ces éléments, typiquement ce type de construction (en C):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    int main(int argc, char **argv)
    {
    int *memory=malloc(typeof(int)*100);
    int *integer=memory;
    integer++;
    free(memory);
    }
    (ouah... obligé d'aller voir le prototype de malloc pour me rappeler comment il marche j'ai failli le confondre avec celui de write)
    Si on devait avoir de l'arithmétique sur des pointeurs unique, je ne vois aucunement le souci.
    Imagines le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    int main(int argc, char **argv)
    {
    unique_ptr<int> memory(new int[100]);
    no_own_ptr<int> integer=memory;
    integer++;
    }
    Évidemment, on ne fait pas l'arithmétique directement sur le pointeur responsable, mais si l'on avait une capsule no_own, comme tu l'appelles, ce code ne poserait aucun souci.
    Ma (**) en bas de message était là pour nuancer, ton raisonnement est loin d'être trivial avec la notion de responsabilité, pour preuve il faut que la capsule n'est pas de responsabilité. J'ai pas dit que c'était impossible/inutile, juste que ça n'avait pas de sens dans le contexte responsabilité, et que c'était pas nécessaire à avoir une sémantique de pointeur.

    Citation Envoyé par Freem Voir le message
    J'ai cru comprendre que auto_ptr n'a comme seul problème celui des transmissions de responsabilité. Pourquoi ne pas l'utiliser, au lieu d'utiliser un outil qui n'est pas du tout fait pour?
    Parce que bon, shared_ptr, à côté de unique/scoped/auto, c'est long et lourd en terme de CPU et de mémoire...
    Tu as benché pour std::shared_ptr ? Je suis pour qu'on réfléchisse avant d'utiliser un pointeur intelligent, mais ce n'est pas pour d'éventuelles meilleurs performances, plutôt pour inciter à se pencher sur le design et déterminer les responsabilités et ne pas mettre des shared_ptr partout.

    (*) De manière général un retour par valeur constante est d'une utilité très limité ...

  20. #20
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Points : 15 620
    Points
    15 620
    Par défaut
    Citation Envoyé par Freem Voir le message
    Il me semble que c'est pour ça que shared_ptr à un mécanisme pour les incorporer: make_shared. J'imagine que s'ils ont pris la peine de faire cet outil, il y a une raison. J'ai du mal à comprendre pourquoi ils n'ont pas utilisé la même logique de construction pour les shared_ptr et unique_ptr. Certes, ces templates ont des rôles distincts, mais je trouve que ça embrouille l'esprit?
    Regarde les figures 2a et 2b du gotw 103, c'est très explicite : http://herbsutter.com/gotw/_103/ (d'ailleurs, je te conseille la lecture des gotw 103, 104 et 105 qui traite spécifiquement des nouveaux pointeurs intelligent)


    Plus généralement sur la discussion, j'ai l'impression que les politiques de gestion des ressources non sont pas claires et que le SRP et Demeter sont encore malmenés.

    Si on a
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class A {
        // fonctions pour une sémantique de collection
        vector<unique_ptr<B>> v;
        B* get(size_t);
     
        // autres fonctions
        ...
    };
    La classe est vue comme une collection de B + une autre sémantique. N'a-t-on pas un problème de respect du SRP ? A priori, je dirais oui et il faudrait donc partager les responsabilité. Et dans ce cas, qu'apporterait une classe CollectionDeB en plus d'un simple typedef ? (ou dit autrement, si une CollectionDeB apporte autre chose, n'est-on pas en face d'une violation de SRP ?)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    typdef vector<unique_ptr<B>> CollectionDeB;
     
    class BHandler {
        BHandler(BIterator begin, BIterator end);
     
        // autres fonctions
        ...
    };
    ou en fonctions libres. On arrive ainsi à une syntaxe similaire aux algorithmes de la STL.

    Autre point, B* get(size_t); est une violation de Demeter. Si elle était respecté, vector<unique_ptr<B>> serait manipulé uniquement en interne et donc celui qui modifie A sait qu'il travaille sur unique_ptr ou sur un pointeur nu issu de unique_ptr et donc qu'il n'a pas la responsabilité. On a une colocalisation de l'information "unique_ptr a la responsabilité de la durée de vie" et l'utilisation de B*. En ne respectant pas Demeter, l'utilisateur de B* ne sait pas si cet objet est issu de unique_ptr ou si c'est un pointeur nu à l'origine. On sépare l'information sur la responsabilité de la durée de vie (indiqué par unique_ptr) et son utilisation, d'où le risque d'erreur (faire un delete sur un objet géré par unique_ptr).

    D'ailleurs ce problème de delete n'est pas spécifique à l'utilisation de unique_ptr. Si on a une classe A qui retourne un B* sans que l'on sache rien sur la responsabilité de la durée de vie, on risque les problèmes. Que l'on utilise unique_ptr ou autre chose. Si un utilisateur fait n'importe quoi avec les pointeurs, unique_ptr ne corrige pas le problème, mais il ne présente pas non plus de problématique supplémentaire.
    En particulier ton code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    unique_ptr<int> x(new int(10));
    foo_c(x.get());
    ...
    foo_c(int *a)
    {
        ...
        a=realloc(malloc(syzeof(int)));
        ...
    }
    pose problème : x n'est plus valide mais n'est pas nullptr. Si tu fais un delete x; tu auras des problème, que tu utilises des pointeurs nus ou des pointeurs intelligent.
    Du coup, ton code suivant ne fonctionne pas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    unique_ptr<int> ptri(new int(10));
    ...
    int* cptri=ptri.release();
    foo_c(cptri);
    ptri.reset(cptri); // cptri ne pointe pas vers la nouvelle ressource
    ...

    Bref, c'est pas un problème de unique_ptr, mais de gestion des responsabilités



    Citation Envoyé par Freem
    Je me demande s'il existe une lib graphique écrite qui ne réinvente pas la roue comme les string et les conteneurs? - Déjà, pas Qt ni wxWidgets. Peut-être Gtk?)
    GTK est une lib C donc qui gère des char*. Pas mieux

    Effectivement, vu ce que tu m'expliques, je n'avais pas compris son rôle premier. Pour le coup, si on a un outil qui n'est bon que dans le cas de l'initialisation de plusieurs pointeurs par le constructeur, quelles sont les différences entre unique_ptr et auto_ptr?
    gotw 42 : http://cpp.developpez.com/gotw/42/

    Citation Envoyé par Freem
    Parce que du coup, il aurait été possible de faire un opérateur de cast pour unique_ptr qui aurait pu pas mal simplifier la vie:
    "operator T * const ()" du fait du const, on sait qu'on ne doit pas modifier le pointeur (et il me semble que le compilo vérifie, mais j'utilise rarement des pointeurs constants donc...), mais on a du coup un pointeur, qui permet de modifier la donnée pointée.
    J'ai l'impression que ça aurait été plus pertinent que de forcer à utiliser get() dès lors que l'on a besoin d'accéder, non pas au pointeur, mais à ses données avec la syntaxe pointeur, chose qui est la plus fréquente, non?
    En fait, on aurait eu un comportement plus proche de celui d'un véritable pointeur, tout simplement, et j'ai toujours vu les pointeurs intelligents comme des pointeurs à la syntaxe proche des bruts, mais à la sémantique plus évoluée. Ici, la sémantique est effectivement plus évoluée, mais la syntaxe pourrait être rendue plus proche...
    Reader Q&A: Why don’t modern smart pointers implicitly convert to *?

Discussions similaires

  1. Réponses: 52
    Dernier message: 23/10/2014, 11h22
  2. Réponses: 19
    Dernier message: 29/09/2014, 17h12
  3. Réponses: 31
    Dernier message: 19/09/2014, 20h40
  4. boost::serialization et std::unique_ptr
    Par Freem dans le forum Boost
    Réponses: 7
    Dernier message: 09/08/2012, 15h08

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo