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

Langage C++ Discussion :

Trimballer un unique_ptr avec la sémantique de mouvement


Sujet :

Langage C++

  1. #21
    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
    @Jean-Marc.Bourguet: Je suis d'accord avec ce que tu dis. En allant plus loin, on ne pourrait pas imaginer quelque chose comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template<class T>
    struct MyClass
    {
      std::unique_ptr<T> ptr;
     
      template<class OwnerPtr>
      MyClass(OwnerPtr&& ptr) : ptr(ptr.release())
      { }
    };
    Qui permettrait de fonctionner avec n'importe quel objet proposant un release (*) retournant un truc qui va bien, ce qui "sémantiquement" correspondrait à n'importe quel objet qui a une responsabilité qu'il peut effectivement transférer (**) ? Ainsi on pourrait avoir un fonctionnement avec des classes SmartPtr plus perso.

    (*) Idéalement je préférerais une fonction libre, pour l'extensibilité.

    (**) Le code ici n'est que partiel, il faut rajouter des check sur le OwnerPtr déduit, entre autre qu'il ne soit pas déduit à un type référence (sinon la responsabilité sera transféré systématiquement) et idéalement checker explicitement un concept (C++14/C++17).

  2. #22
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais avant d'aller plus loin, je dois préciser que je considère généralement que le unique_ptr n'est qu'un "détail d'implémentation" de la classe qui le manipule.

    Cette manière de considérer les choses oriente -- fatalement -- ma réflexion

    Je trouves en effet dommage d'exposer un tel détail d'implémentation s'il est possible de l'éviter, fusse au niveau du constructeur.
    Je réagi la dessus, je trouve le passage de pointeur à un constructeur peut clair : on est obligé de se réferrer à la doc pour savoir si l'objet créé devient propriétaire (et donc delete le pointeur correctement en fin vie) du pointeur passé en paramètre ou pas.
    Avec un unique_ptr il n'y à pas de doute possible.

    C'est plus qu'un détail d'implémentation, c'est un "morceau de documentation intégré au prototype de la fonction"

  3. #23
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Je réagi la dessus, je trouve le passage de pointeur à un constructeur peut clair : on est obligé de se réferrer à la doc pour savoir si l'objet créé devient propriétaire (et donc delete le pointeur correctement en fin vie) du pointeur passé en paramètre ou pas.
    Avec un unique_ptr il n'y à pas de doute possible.

    C'est plus qu'un détail d'implémentation, c'est un "morceau de documentation intégré au prototype de la fonction"
    C'est une manière de voir

    Mais, d'un autre coté:
    1. Ca donne à ta classe une responsabilité qu'elle ne devrait pas avoir, à savoir de "libérer" la classe qui fournit le pointeur de la responsabilité de la gestion de la durée de vie du pointeur sous-jacent.
    2. Cela oblige, si pas la création d'un unique_ptr temporaire, à exposer ce qui n'est aussi qu'un détail d'implémentation d'une autre classe (ou du moins ce qui ne devrait être qu'un détail d'implémentation d'une autre classe)
    3. Tu perds en "expressivité" le fait que la classe qui fournit le pointeur est effectivement "libérée" de la responsabilité de la durée de vie du pointeur sous-jacent
    4. Tu restes avec des règles qui ne disent pas explicitement ce que tu dois faire avec tes pointeurs nus, alors que si tu transmets systématiquement tes pointeurs nus, à charge pour la classe qui les utilise de décider si elle en prend la responsabilité ou non et à l'utilisateur de veiller à ce que les classes qui les fournissent le fasse "selon le manuel" (la doc ), tu peux commencer à dire : vous ne faites plus jamais un delete explicite, vous veillez à ce qu'une classe (ou un autre) prenne la responsabilité de le faire (en utilisant une forme ou l'autre de pointeur intelligent)
    5. Cela te force à réfléchir à ta classe non plus comme l'ensemble des données qu'elle contient, mais bien en terme des services qu'elle est sensée rendre, à savoir: être ou non en mesure de "prêter" un pointeur dont elle garde la responsabilité et / ou être en mesure (ou non) de donner la responsabilité du pointeur qu'elle transmet
    Personnellement, je trouve le dernier point pour le moins intéressant, car, si tu as une classe qui a décidé de ne jamais se départir de la responsabilité de la gestion de la durée de vie du pointeur sous-jacent, elle présentera un comportement proche du get de unique_ptr, mais pas un comportement proche du release de unique_ptr.

    Autrement dit, tu es alors en mesure d'interdire à l'utilisateur d'essayer de transmettre un pointeur dont la responsabilité de la durée de vie n'aurait pas été "mise sur le marché", chose que tu ne pourras pas faire en considérant unique_ptr comme une information sémantique dans ton code


    En outre, le fait qu'une classe prend la responsabilité de la durée de vie d'un objet est suffisamment important pour devoir, de toutes façons, être indiqué de manière explicite dans le cartouche de la classe en question, et ne pas se limiter à la seule connaissance de son constructeur.

    De plus, il me semble logique que ce soit au développeur de la classe de décider, selon le contexte, ce que la classe peut et ne peut pas faire avec le(s) objets dont elle doit gérer la durée de vie, et, surtout, qu'il puisse obliger l'utilisateur de sa classe à respecter sa décision.

    Il peut donc décider de fournir (ou non)
    1. un comportement qui libère sa classe de la responsabilité de l'objet (ou des objet) au(x)quel(s) elle donne accès, en disant en gros "je te le donne, tu tires ton plan avec, moi, je m'en lave les mains"
    2. un comportement qui ne fait que "prêter" l'objet (ou les objets) tout en en gardant la responsabilité
    3. un comportement qui aurait pour résultat la destruction "programmée" de l'objet (l'équivalent de la fonction reset de unique_ptr)
    (aucune de ces possibilité n'étant obligatoire ni exclusive bien entendu )

    Enfin, en tant que développeur de ta classe, lorsque tu demandes un unique_ptr comme argument, tu indiques clairement le fait que tu t'attends à ce que la responsabilité de la gestion de vie de l'objet sous-jacent soit déjà prise en charge par "quelque chose" (même si ce n'est que par le unique_ptr créé pour l'occasion ).

    Je trouve, pour ma part, beaucoup plus intéressant de partir du principe que la durée de vie de l'objet en question est "à prendre", à charge pour l'utilisateur de veiller à ce que ce soit effectivement le cas.

    De cette manière, si la responsabilité de la durée de vie est déjà prise par "quelque chose", l'utilisateur se trouve face à l'obligation de la libérer de manière explicite, ce qui correspondra à un service que cette "autre chose" sera en mesure (ou non) de fournir, et, d'un autre coté, il ne doit pas commencer à prendre en compte le fait que certaines classes vont avoir besoin d'un unique_ptr (ou d'un autre pointeur intelligent) dans le reste de son développement (au niveau d'une éventuelle fabrique, par exemple).

    Je vais digresser assez bien du problème original, mais ce ne sera pas la première fois

    Prenons le cas d'une fabrique, patron de conception bien connu.

    A priori, il ne devrait y avoir qu'une et une seule classe qui manipule la fabrique, dans une optique de "centralisation" des problèmes.

    Sauf que, en pratique, il y a parfois plusieurs utilisateurs de la fabrique

    Si l'on a plusieurs utilisateurs possibles, on se trouve face à la possibilités que certains préfèrent recevoir un unique_ptr et d'autres un shared_ptr (ce n'est que pour l'exemple, hein )

    De deux choses l'une : soit tu dis que ta fabrique renvoie (selon ton bon vouloir, égoïste et arbitraire) un unique_ptr ou un shared_ptr, et tu forces tous les utilisateurs à l'accepter comme tel, soit tu prévois une possibilité pour renvoyer un unique_ptr et une autre pour renvoyer un shared_ptr. Et tant qu'à faire, car on ne sait jamais, tu prévois une possibilité pour renvoyer un weak_ptr et un pointeur nu et -- soyons fous-- les différentes possibilités offertes par boost, tiens, juste "pour au cas où".

    Aucune de ces deux solutions n'est réellement satisfaisante en soi :
    La première parce que cela va obliger certains utilisateurs à transférer l'objet récupéré de unique_ptr vers un shared_ptr ou inversement, la deuxième parce que, partis comme nous le serions, nous ne tarderions pas à envisager encore d'autres solutions, et que notre fabrique, élément pourtant simple à la base, finirait par ressembler à une hydre à neuf têtes

    Encore une fois, la solution la plus simple est toujours la moins compliquée : je te file un pointeur nu, et tu tires ton plan avec (sous entendu : tu choisis toi-même la manière dont tu envisages d'en gérer la durée de vie).

    Je reviens maintenant sur le problème original tel que je le conçois:

    En tant que développeur d'une classe qui prendra la responsabilité de la durée de vie d'un pointeur, tu ne sais, a priori, pas d'où viendra le pointeur dont ta classe va prendre la responsabilité, et tu n'as aucune raison de t'en soucier.

    Tout ce dont tu as à te soucier, c'est de faire en sorte que ta classe fasse ce pour quoi elle est prévue et donc, qu'elle soit en mesure de libérer la mémoire allouée à l'objet (aux objets) dont elle a la responsabilité.

    C'est (entre autres) ce pour quoi ta classe est créée, c'est ce qu'elle doit faire, et ça s'arrête là.

    Sachant cela, c'est à l'utilisateur de faire en sorte que les prérequis nécessaires à l'utilisation de ta classe soit respectés.

    La manière dont il s'y prendra t'importe peu: ce qui importe, c'est que lorsqu'il transmette à ta classe un objet dont elle sera la seule à décider de la durée de vie, le pointeur soit effectivement "libre de toute attache" et que si une classe qu'il utilise est "suffisamment égoïste" (on se comprend sur le terme hein ) pour refuser de rendre la responsabilité de la durée de vie de l'objet qu'elle contient, hé bien, que l'utilisateur ne puisse pas la forcer à le faire
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #24
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @koala01 :
    1 - question de point de vue, quand on donne on pointeur à un constructeur, j'aile bien savoir ce qu'il devient (exemple: Qt)
    2 - on est d'accord, même si un std::make_unique fait son apparition, il souffrira des même problêmes que std::make_shared : un accès au constructeur difficile.
    3 - effectivement, mais il est possible de rendre l'objet en question constructible depuis le "gestionnaire" (la classe qui possède une fonction pour créer l'objet)
    4 - perso, je préfère éviter de transférer des pointeurs nus pour éviter les ambiguïtés.(et si je passe un pointeur nu, il y aura des commentaires, pour savoir si l'appelant ou l'appelé doit gérer la durée de vie.
    5 - c'est exactement comme ça que je vois une classe : "un truc qui me transforme un machin en bidule en effectuant un service. "

    Pour tes 3 propositions de fonctionnalités, pour moi elles sont exclusive :
    1ere utilisation d'un unique_ptr
    2eme shared_ptr
    3eme ? J'ai du mal à saisir l'utilité.

    Citation Envoyé par koala01 Voir le message
    Aucune de ces deux solutions n'est réellement satisfaisante en soi :
    La première parce que cela va obliger certains utilisateurs à transférer l'objet récupéré de unique_ptr vers un shared_ptr ou inversement, la deuxième parce que, partis comme nous le serions, nous ne tarderions pas à envisager encore d'autres solutions, et que notre fabrique, élément pourtant simple à la base, finirait par ressembler à une hydre à neuf têtes

    Encore une fois, la solution la plus simple est toujours la moins compliquée : je te file un pointeur nu, et tu tires ton plan avec (sous entendu : tu choisis toi-même la manière dont tu envisages d'en gérer la durée de vie).
    C'est vrai que vu comme ça l'utilisation de pointeurs nus peut être valide (à défaut de trouver mieux )
    (et oui j'ai valider mon message avant de le finir, c'est chiant que "envoyer" soit le premier bouton à prendre le focus après un tab.)

  5. #25
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    @koala01 :
    1 - question de point de vue, quand on donne on pointeur à un constructeur, j'aile bien savoir ce qu'il devient (exemple: Qt)
    Justement, tu auras beaucoup plus facile à garder l'homogénéité de la chose en ne transmettant le "owner" que sous la forme de pointeur!

    Autrement, tu vas devoir fournir le choix dans ton constructeur, quelque chose de proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Class MaClass{
        public:
            MaClass( /* je mets quoi ici ??? */ owner);
    };
    A priori, on dirait bien un unique_ptr, mais... En est on si sur que ca
    2 - On est d'accord, même si on std::make_unique fait son apparition, il souffrira des même problêmes que std::make_shared : un accès au constructeur.
    Même si make_unique fait son apparition, on en restera sur le fait que, quoi qu'il advienne, un constructeur prenant un unique_ptr tend à violer la loi de Demeter dés que le pointeur sous-jacent est déjà géré par ailleurs
    3 - effectivement, mais il est possible de rendre l'objet en question constructible depuis le "gestionnaire" (la classe qui possède une fonction pour créer l'objet)
    Surtout pas malheureux : le gestionnaire, il gère, la fabrique, elle construit !

    De cette manière, tu es sur que le gestionnaire n'a besoin (et encore ) que du fichier d'en-tête du type de base, et qu'il peut se contenter de ne connaitre les objets qu'il gère que comme étant du type de base.

    Si tu permet au gestionnaire de fabriquer ses propres objets (j'avoue ici être de plus en plus critique au sujet du clonage ), cela implique qu'il disposera des fichiers d'en-tête pour l'ensemble des types qu'il doit gérer.

    Rendu à ce point, il devient très (trop) facile de se dire "bah, j'ai le fichier d'en-tête, je vais en profiter pour faire un dynamic_cast bien crade pour tester le type réel de mon objet".

    Tu sais, un code très proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if (dynamic_cast<UnType*>(monPointeur){
        /* tu mets ce que tu veux ici */
    }
    Puis, voyant que tu as obtenu (le contraire serait malheureux) ce que tu voulais, tu irais un peu plus loin, en testant un deuxième type. Quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    if (dynamic_cast<UnType*>(monPointeur){
        /* tu mets ce que tu veux ici */
    }else if (dynamic_cast<UnAutreType*>(monPointeur){
        /* tu mets ce que tu veux ici */
    }
    Puis, voyant que ca marche pour ton gestionnaire, bah, tu étendrais le principe à l'une puis à l'autre fonction prenant un pointeur sur ton type de base.

    Et en moins de temps qu'il ne le faudrait pour que je l'écrive, tu te retrouverais avec un projet qui ne respecte plus l'OCP nulle part, et tu dirais adieux à tout espoir d'apporter des évolutions sans devoir modifier ton code en profondeur (ben oui, à chaque nouveau type dérivé, tu devras t'assurer que chacune des fonctions dans lesquelles tu as utilisé cette horreur prend le nouveau type en compte)
    4 - perso, je préfère éviter de transférer des pointeurs nus pour éviter les ambiguïtés.(et si je passe un pointeur nu, il y aura des commentaires, pour savoir si l'appelant ou l'appelé doit gérer la durée de vie.
    Pourquoi, si les règles sont claires et que la responsabilité de la classe clairement établie

    Si tu pars avec une règle proche de
    !!! Aucune fonction prenant un pointeur nu ne peut détruire le pointeur
    c'est simple, clair et efficace.
    La deuxième règle prend la forme de
    La gestion de la durée de vie des pointeurs est déléguées à des classes spécialement prévues à cet effet, qui utiliseront des pointeurs intelligents (comme cela je ne me limite pas aux unique_ptr) pour le faire
    Ca te règle, en plus, tous les problème de résistances aux exceptions (ce qui est l'un des attraits majeurs des pointeurs intelligents )

    En deux règles, tu te simplifies la vie de manière incroyable
    5 - c'est exactement comme ça que je vois une classe : "un truc qui me transforme un machin en bidule en effectuant un service. "
    Non, un classe, en tant qu'utilisateur, c'est quelque chose qui rend "un ou plusieurs services" et dont on n'a pas à s'inquiéter de la manière dont elle le(s) rend


    Pour tes 3 propositions de fonctionnalités, pour moi elles sont exclusive :
    • 1ere utilisation d'un unique_ptr
    • 2eme shared_ptr
    • 3eme ? J'ai du mal à saisir l'utilité.
    Non, pas du tout!

    La preuve que ce n'est pas aussi simple, c'est que la question de notre ami oodini parlait à la base de la sémantique de mouvement sur un unique_ptr.

    autrement dit, on parle bel et bien de "libérer quelque chose" de la responsabilité (unique et non partagée!) de la gestion de la durée de vie d'un objet (qui est unique) afin de donner cette responsabilité "à autre chose".

    D'ailleurs, si tu y regardes d'un tout petit peu plus près, tu remarqueras que l'on dispose strictement de ces trois possibilités au niveau de std::unique_ptr avec les fonctions respectives release, get et reset

    Même la norme a prévu que ces comportements pouvaient exister de manière indépendante et non exclusive
    [EDIT]En fait, la troisième te permettrait, par exemple, de maintenir un ensemble de unique_ptr dans une collection quelconque (mettons, une std::vector<std::unique_ptr<unType>>) de taille (plus ou moins) fixe, dont certains seraient invalidés (dont le pointeur sous-jacent serait à nullptr).

    De cette manière, tu t'évites (dans la limite du nombre de unique_ptr invalides) d'avoir à en recréer systématiquement chaque fois que tu veux rajouter un nouvel élément (et, accessoirement, d'avoir à augmenter la taille de ta collection).

    Après tout, la classe prend la responsabilité de la durée de vie de l'objet, soit, mais est-ce pour cela que l'objet en question doit être "condamné" à survivre tant que l'instance de la classe survit
    [/EDIT]

    C'est vrai que vu comme ça l'utilisation de pointeurs nus peut être valide (à défaut de trouver mieux )
    Mais, surtout, cela n'impose pas un choix à l'utilisateur qui, quelle que soit la solution envisagée, trouvera toujours le moyen de se plaindre en disant qu'il aurait préféré l'autre!

    En plus, le role d'une fabrique, comme je l'ai dit plus haut, c'est de construire les objets, et non de s'inquiéter de la manière dont ils seront gérés une fois qu'ils sont "sortis d'usine"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #26
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Je comprends pas trop tes réticences vis à vis de unique_ptr.

    Je trouve qu’il a plein d’avantages :
    - une sémantique bien définie
    - une robustesse et exception safety (en particulier avec make_unique) excellentes, que tu n’auras jamais avec des pointeurs nus
    - il est standard, donc connu logiquement de tout programmeur

    Alors bien sûr, il ne convient pas à tous les usages. Mais pour un transfert d’ownership, j’ai du mal à voir plus adapté…

    Même si make_unique fait son apparition, on en restera sur le fait que, quoi qu'il advienne, un constructeur prenant un unique_ptr tend à violer la loi de Demeter dés que le pointeur sous-jacent est déjà géré par ailleurs
    La sémantique d’une méthode prenant un unique_ptr est claire : elle prend l’ownership sur le pointeur. Si le pointeur est déjà géré par ailleurs, le contrat n’est pas respecté --> comportement indéfini, et c’est de la faute de l’appelant. Je crois qu’on peut laisser Demeter en dehors de ça, elle a déjà fort à faire par ailleurs.

  7. #27
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je comprends pas trop tes réticences vis à vis de unique_ptr.

    Je trouve qu’il a plein d’avantages :
    - une sémantique bien définie
    - une robustesse et exception safety (en particulier avec make_unique) excellentes, que tu n’auras jamais avec des pointeurs nus
    - il est standard, donc connu logiquement de tout programmeur
    Contrairement à ce que tu sembles croire, je n'ai strictement aucune réticence à utiliser les unique_ptr, bien au contraire

    j'ai des réticences à les exposer à partir du moment où ils sont manipulé au sein d'une classe, parce que ca a tendance à exposer des détails d'implémentation à l'utilisateur au sens de la loi de Déméter.

    Je suis on ne peut plus d'accord quant aux bienfaits de cette classe, mais
    Alors bien sûr, il ne convient pas à tous les usages. Mais pour un transfert d’ownership, j’ai du mal à voir plus adapté…
    Le fait est qu'en l'exposant au niveau du constructeur (ou de toute autre fonction membre d'une classe):
    1. Tu zigzague à la limite extérieure de la loi de déméter
    2. Tu t'arroges le droit de faire quelque chose qui n'est pas de ta responsabilité en tant que développeur de la classe (à savoir "voler" la responsabilité de la durée de vie à une autre classe que la tienne)
    3. Tu te bases sur la présence du paramètre d'une fonction, qui sera peut être "perdue" au milieu de toutes les autres, pour estimer que cela documente suffisamment le fait que ta classe prend la responsabilité du pointeur, alors que je trouves cela insuffisant et d'une certaine manière dangereux.
    4. La résistance aux exceptions est, dans le cas où tu le passe par valeur en paramètre du constructeur sans doute encore pire que si tu passais le pointeur nu (*)
    (*) Ben oui, comme le unique_ptr est déjà créé, il a déjà pris la responsabilité de la durée de vie du pointeur sous-jacent ce qui fait que, si tu as une exception qui survient dans le constructeur, l'objet en question est d'office détruit, sans avoir l'occasion de le récupérer par ailleurs, alors que si tu le passes sous la forme d'un pointeur nu, il te "reste une chance" de pouvoir le faire
    La sémantique d’une méthode prenant un unique_ptr est claire : elle prend l’ownership sur le pointeur.
    Claire, au niveau de la fonction qui reçoit le pointeur, mais insuffisante au niveau de la classe, car, à mon sens, il aura trop vite fait de passer inaperçu, surtout si tu t'es amusé à créer un alias de type
    Si le pointeur est déjà géré par ailleurs, le contrat n’est pas respecté --> comportement indéfini, et c’est de la faute de l’appelant.
    Mais à coté de cela, tu lui donnes l'occasion de choisir en connaissance de cause d'utiliser ou non ta classe
    Je crois qu’on peut laisser Demeter en dehors de ça, elle a déjà fort à faire par ailleurs.
    Hummm... Dois-je te rappeler que Demeter est un principe de conception et qu'aucune fonctionnalité offerte par le langage (de manière volontaire ou non) ne peut faire en sorte qu'une mauvais décision de conception devienne bonne "par magie"

    Mais on a déjà eu ce genre de conversation
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #28
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    On dirait que j'ai posé une bombe. Dommage que je n'ai pas le temps de compter les abattis projetés sur les murs.

    J'essaierai de lire ça ce soir (avec intérêt !).

  9. #29
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par koala01 Voir le message
    j'ai des réticences à les exposer à partir du moment où ils sont manipulé au sein d'une classe, parce que ca a tendance à exposer des détails d'implémentation à l'utilisateur au sens de la loi de Déméter.
    On ne regarde vraiment pas la même face du cube. Pour moi, accepter un unique_ptr en paramètre ne veut nullement dire que tu en stockes un en interne. ça veut juste dire : je m’approprie ce pointeur.

    [*]Tu zigzague à la limite extérieure de la loi de déméter[*]Tu t'arroges le droit de faire quelque chose qui n'est pas de ta responsabilité en tant que développeur de la classe (à savoir "voler" la responsabilité de la durée de vie à une autre classe que la tienne)
    Je ne vole rien, l’utilisateur me donne. S’il ne veut pas me donner, c’est qu’il n’a pas besoin de mon service, mais de celui de quelqu’un d’autre . Et pour déméter : encore une fois, je n’expose aucun état interne. Seulement une interface.

    [*]Tu te bases sur la présence du paramètre d'une fonction, qui sera peut être "perdue" au milieu de toutes les autres, pour estimer que cela documente suffisamment le fait que ta classe prend la responsabilité du pointeur, alors que je trouves cela insuffisant et d'une certaine manière dangereux.
    C’est suffisant car le compilateur vérifie que l’utilisateur n’appelle pas ma fonction avec n’importe quoi. Quand on est obligé d’utiliser std::move, on se pose les bonnes questions. unique_ptr n’a pas les problèmes d’auto_ptr.

    [*]La résistance aux exceptions est, dans le cas où tu le passe par valeur en paramètre du constructeur sans doute encore pire que si tu passais le pointeur nu (*)
    (*) Ben oui, comme le unique_ptr est déjà créé, il a déjà pris la responsabilité de la durée de vie du pointeur sous-jacent ce qui fait que, si tu as une exception qui survient dans le constructeur, l'objet en question est d'office détruit, sans avoir l'occasion de le récupérer par ailleurs, alors que si tu le passes sous la forme d'un pointeur nu, il te "reste une chance" de pouvoir le faire
    Là tu t’égares. Dans le cas d’un pointeur nu, tu ne sais pas ce qu’il est advenu de ton objet : il peut lui être arrivé n’importe quoi, en particulier il peut avoir été désalloué par la méthode appelée (elle a le droit, elle a pris l’ownership) comme ne pas l’être, modifié et trituré dans tous les sens, et tu n’as aucun moyen de savoir quoi que ce soit ! Dans le cas d’un unique_ptr, je suis sûr que s’il y a eu transfert d’ownership, alors l’objet a été détruit, et sinon, il n’a pas été modifié (et je peux tester le succès de ce transfert).

    Je préfère un comportement déterminé qui n’est peut-être pas celui qui m’arrange le plus qu’un comportement indéterminé dont je ne sais pas quoi faire.

    Hummm... Dois-je te rappeler que Demeter est un principe de conception et qu'aucune fonctionnalité offerte par le langage (de manière volontaire ou non) ne peut faire en sorte qu'une mauvais décision de conception devienne bonne "par magie"

    Mais on a déjà eu ce genre de conversation
    Je maintiens que Déméter n’a pour le coup pas grand chose à voir avec la choucroute. Ce n’est pas parce que j’expose un unique_ptr dans l’interface que je stocke l’objet comme tel.

  10. #30
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Pour moi, c'est du même ordre qu'ajouter le qualificatif const à un argument.

    Il peut être inutile du point de vue fonctionnel par rapport au service rendu. Mais il donne des garanties à l'utilisateur de la fonction sur le devenir de l'argument passé.

  11. #31
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    On ne regarde vraiment pas la même face du cube. Pour moi, accepter un unique_ptr en paramètre ne veut nullement dire que tu en stockes un en interne. ça veut juste dire : je m’approprie ce pointeur.
    Ah,oui, cela veut dire que tu vas t'amuser à prendre un unique_ptr pour t'amuser après à le transformer en shared_ptr ou n'importe quoi d'autre

    En plus, si l'on reprend la question d'oodini, il était bel et bien de le manipuler en interne comme un unique_ptr :-
    Je ne vole rien, l’utilisateur me donne. S’il ne veut pas me donner, c’est qu’il n’a pas besoin de mon service, mais de celui de quelqu’un d’autre .
    Mais tu prends une responsabilité que tu n'as pas à prendre : celle de dégager "ce qui avait" la responsabilité de la durée de vie du pointeur de cette responsabilité. (et encore : tu prétens quelque part le faire, alors que, tout ce que tu fais, c'est décider que ta classe détruira ce pointeur quand elle mourra, sans forcément veiller à ce que l'objet ou la fonction qui te transmet ce pointeur s'en sera effectivement completement dégagée)
    Et pour déméter : encore une fois, je n’expose aucun état interne. Seulement une interface.
    Si tu exposais seulement une interface, tu n'exposerais que les fonction reset, release et get de unique_ptr

    Là, tu exposes beaucoup plus que l'interface
    C’est suffisant car le compilateur vérifie que l’utilisateur n’appelle pas ma fonction avec n’importe quoi.
    Ah, et comment veux tu qu'il le vérifie

    Si, d'une manière ou d'une autre, la durée de vie de l'objet pointé est gérée "par ailleurs" unique_ptr n'a strictement aucun moyen de s'en assurer
    Quand on est obligé d’utiliser std::move, on se pose les bonnes questions. unique_ptr n’a pas les problèmes d’auto_ptr.
    C'est généralement unique_ptr qui bouge, pas le pointeur en lui-même

    J'ai fait l'expérience pas plus tard que hier, sur une std::map<int, std::unique_ptr<T>> : je crées un pointeur et je le manipule (j'en profites au passage pour en vérifier l'adresse), puis je l'ajoute à la map en utilisant emplace(std::unique_ptr<T>>(monPointeur)


    J'ai repris par la suite l'adresse du pointeur sous-jascent, il n'avait pas bougé

    Et c'est normal, un pointeur n'est jamais qu'une valeur numérique entière, et donc, parfaitement copiable (je parle du pointeur, là, pas de l'objet qui se trouve à l'adresse indiquée par le pointeur )
    Là tu t’égares. Dans le cas d’un pointeur nu, tu ne sais pas ce qu’il est advenu de ton objet : il peut lui être arrivé n’importe quoi, en particulier il peut avoir été désalloué par la méthode appelée (elle a le droit, elle a pris l’ownership) comme ne pas l’être, modifié et trituré dans tous les sens, et tu n’as aucun moyen de savoir quoi que ce soit ! Dans le cas d’un unique_ptr, je suis sûr que s’il y a eu transfert d’ownership, alors l’objet a été détruit, et sinon, il n’a pas été modifié (et je peux tester le succès de ce transfert).
    Ce qui fait que, non seulement, tu loupe la création de l'objet qui devait prendre la responsabilité de ton pointeur, mais, en plus, tu es bon pour recréer l'objet d'origine bravo

    La prise de responsabilité au niveau du shared_ptr est garantie noexcept, ce qui est normal car, au pire, il s'agit juste d'appeler delete (qui est garanti noexcept itou) sur le pointeur détenu au par avant.

    Si tu reportes ca à la dernière étape de ton constructeur, après tout ce qui risque de lancer une exception, tu as la garantie que, le pointeur que tu voulais transmettre n'aura pas bougé et que tu peux le récupérer.

    Au contraire, si tu forces la création d'un unique_ptr pour pouvoir le transmettre, et que le constructeur de ta classe vient à lancer une exception, tu peux être sur de devoir te retaper tout le boulot de construction / mise à jour du pointeur!!! Chapeau, pourquoi faire simple quand on peut faire compliqué


    Je préfère un comportement déterminé qui n’est peut-être pas celui qui m’arrange le plus qu’un comportement indéterminé dont je ne sais pas quoi faire.
    le comportement que je te propose est beaucoup plus déterministe (et quelque part beaucoup plus résistant aux exceptions) que le tien!


    Le principe ici est simple : tant que tu ne donnes rien au constructeur de unique_ptr, le pointeur qu'il maintient en interne va pointer sur nullptr.

    Delete sur nullptr, c'est une no-op. Donc, si tu attends d'être sur que "tout ce qui pouvait foirer" a réussi avant de lui filer le pointeur, et que tu appelles reset sur ton unique_ptr en lui filant le pointeur dont tu veux qu'il prenne la responsabilité, tu sais que sa prise de responsabilité se limitera à l'affectation d'une valeur numérique, ce qui n'est pas ce qui prend le plus de temps!

    Mais, en attendant, tu es sur de garder le pointeur que tu veux filer à ton unique_ptr "en l'état" tant que quelque chose risque de foirer
    Je maintiens que Déméter n’a pour le coup pas grand chose à voir avec la choucroute. Ce n’est pas parce que j’expose un unique_ptr dans l’interface que je stocke l’objet comme tel.
    Je te pose donc la question, tu vas le stocker comment

    Et, si l'on se réfère à la question d'oodini, c'est bel et bien sous cette forme que tu veux le stocker

    De toutes manières, quand bien même tu ne ferais que le manipuler sous cette forme avant de le transformer en "autre chose" (*)le fait que tu le manipule en tant que unique_ptr ne regarde que toi, quand tu es dans le cadre du développement de ta classe

    (*)Ce qui serait quelque part parfaitement aberrant, car tu te retrouveras forcément à devoir libérer ton unique_ptr de sa responsabilité pour pouvoir la transférer "à autre chose" et tu te retrouves donc avec deux constructions (celle du unique_ptr et celle de "l'autre chose", quelle qu'elle soit), une destruction et un "passage de flambeau", alors que tu aurais pu te contenter d'une seule construction et (dans le pire des cas) d'un passage de flambeau!

    Honnêtement, je préfères de loin les avantages que j'ai à ne pas exposer mon unique_ptr à tout le monde à ceux que estimes avoir en le faisant
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #32
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Ah,oui, cela veut dire que tu vas t'amuser à prendre un unique_ptr pour t'amuser après à le transformer en shared_ptr ou n'importe quoi d'autre ?
    Ceci est exactement ce que je me suis tué à dire dans mes X derniers posts, et c'est exactement ce que j'applique, c'est la logique même -_-

    Edit: voire pire, c'est ta logique préférée à la base, écrire l'interface indépendamment de l'implémentation...

  13. #33
    Expert éminent

    Inscrit en
    Novembre 2005
    Messages
    5 145
    Détails du profil
    Informations forums :
    Inscription : Novembre 2005
    Messages : 5 145
    Points : 6 911
    Points
    6 911
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Justement, tu auras beaucoup plus facile à garder l'homogénéité de la chose en ne transmettant le "owner" que sous la forme de pointeur!

    Autrement, tu vas devoir fournir le choix dans ton constructeur, quelque chose de proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Class MaClass{
        public:
            MaClass( /* je mets quoi ici ??? */ owner);
    };
    Tu me permettras de me souvenir avoir entendu cet argument contre les std::string et std::vector, n'avoir pas été convaincu alors et de le considérer comme encore moins valable de nos jours, l'expérience ayant confirmé sa faible valeur.

    Même si make_unique fait son apparition, on en restera sur le fait que, quoi qu'il advienne, un constructeur prenant un unique_ptr tend à violer la loi de Demeter dés que le pointeur sous-jacent est déjà géré par ailleurs
    J'ai jamais trop accepté la loi de Demeter comme bonne. Où plutôt, si le principe sous-jacent est bon, l'appliquer au niveau de la classe est inutilement restrictif, ne pas considérer la nature des classes est absurde et son application mécanique -- comme l'application mécanique de tout principe de conception, mais bon quand on appelle son principe une loi, ça commence déjà mal -- m'a toujours semblé tenir du fétichisme. Demeter entre modules métiers, ça me semble bien. Demeter entre une classe métier et une classe technique, non. Si utiliser unique_ptr<> dans une interface est contraire à la loi de Demeter, ça ne risque que de me conforter dans mon opinion que sa formulation est nuisiblement restrictive.

    Citation Envoyé par koala01 Voir le message
    j'ai des réticences à les exposer à partir du moment où ils sont manipulé au sein d'une classe, parce que ca a tendance à exposer des détails d'implémentation à l'utilisateur au sens de la loi de Déméter.
    Je me fous de comment c'est géré à l'intérieur. Les unique_ptr dans l'interface définissent une interface, pas comment ce doit être géré à l'intérieur (même s'ils sont bien pratiques pour ça aussi).

    Le fait est qu'en l'exposant au niveau du constructeur (ou de toute autre fonction membre d'une classe):
    [*]Tu t'arroges le droit de faire quelque chose qui n'est pas de ta responsabilité en tant que développeur de la classe (à savoir "voler" la responsabilité de la durée de vie à une autre classe que la tienne)
    Voler? J'ai du mal à comprendre. Il me semblait qu'on parlait du choix de l'interface d'un composant qui de toute manière considèrera qu'il a seul la responsabilité de la libération.

    [*]Tu te bases sur la présence du paramètre d'une fonction, qui sera peut être "perdue" au milieu de toutes les autres, pour estimer que cela documente suffisamment le fait que ta classe prend la responsabilité du pointeur, alors que je trouves cela insuffisant et d'une certaine manière dangereux.
    Ou il y a un std::move, ou la source de l'unique_ptr est une fonction qui en renvoie un, ce qui ne se fait pas par hasard (tu ne peux pas non plus le "voler" par un simple retour d'une vari

    [*]La résistance aux exceptions est, dans le cas où tu le passe par valeur en paramètre du constructeur sans doute encore pire que si tu passais le pointeur nu (*)
    (*) Ben oui, comme le unique_ptr est déjà créé, il a déjà pris la responsabilité de la durée de vie du pointeur sous-jacent ce qui fait que, si tu as une exception qui survient dans le constructeur, l'objet en question est d'office détruit, sans avoir l'occasion de le récupérer par ailleurs, alors que si tu le passes sous la forme d'un pointeur nu, il te "reste une chance" de pouvoir le faire
    J'ai du mal à voir comment tu peux savoir si l'exception intervient avant l'appel (auquel cas tu peux faire quelque chose) ou après que le composant ait déjà pris la responsabilité (auquel cas c'est un bug s'il ne l'a pas libéré). À moins que tu ne découpes plus ton code mais la technique est aussi possible avec un unique_ptr.

    Claire, au niveau de la fonction qui reçoit le pointeur, mais insuffisante au niveau de la classe, car, à mon sens, il aura trop vite fait de passer inaperçu, surtout si tu t'es amusé à créer un alias de type
    À nouveau, un std::move ne va pas passer inaperçu.
    Les MP ne sont pas là pour les questions techniques, les forums sont là pour ça.

  14. #34
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Jean-Marc.Bourguet Voir le message
    J'ai du mal à voir comment tu peux savoir si l'exception intervient avant l'appel (auquel cas tu peux faire quelque chose) ou après que le composant ait déjà pris la responsabilité (auquel cas c'est un bug s'il ne l'a pas libéré). À moins que tu ne découpes plus ton code mais la technique est aussi possible avec un unique_ptr.
    Comme je l'ai dit, ca, tu peux t'en prémunir!

    Repenses, simplement, à la logique que l'on met en place lorsqu'il faut redéfinir l'opérateur d'affectation:

    On fait d'abord les allocations de mémoire, les copies et tout ce qu'il faut, et ce n'est que quand il n'y a plus de risque que "quelque chose" foire que tu swapes les données.

    En forçant l'unique_ptr à exister pour qu'il soit passé en argument, ce sera la première chose qui sera détruite, et le pointeur sous-jacent avec, alors que, si tu reçois un pointeur nu, tant qu'il reste nu, celui qui a appelé la fonction peut encore toujours le récupérer "si quelque chose foire", sans que cela ne t'empêche d'utiliser le pointeur en cas de besoin, ni d'effectivement transférer la propriété de ce dernier au unique_ptr une fois que tout est fait.

    Je suis le premier à remercier le comité de nous avoir donné cette classe, et j'aurais énormément de mal à m'en passer.

    Mais le premier message envoyé par cette classe n'est, ni plus ni moins que "faites gaffe avec ce pointeur, car, quand je suis détruit, quelles que soient les circonstances, l'objet pointé par ce pointeur sera aussi détruit".

    Cette classe ne fait rien d'autre que ça : assurer la destruction correcte de l'objet qui se trouve à l'adresse indiquée par le pointeur sous-jacent en temps et en heure (éventuellement sur demande grâce à reset, au plus tard à la destruction de unique_ptr au travers de son destructeur), tout en permettant "à qui veut" de récupérer le pointeur tant qu'il existe (au travers de la fonction get) ou même en acceptant d'être "libéré" de cette responsabilité (au travers de la fonction release).
    À nouveau, un std::move ne va pas passer inaperçu.
    Et où espères tu le voir, ton move dans ta fonction l'utilisateur n'a pas à s'en inquiéter! Dans le code de l'utilisateur à condition qu'il ne décide pas d'appelerla fonction sous la forme de foo(std::unique_ptr<T>(monpointeur).

    Je suis pour donner la responsabilité (commentée par le sens de la classe ou par la doc!) de veiller à transmettre un pointeur dont il a la certitude que rien n'essayera de le détruire "par ailleurs", mais là, on atteint l'effet inverse en en demandant trop à l'utilisateur vu qu'on lui demande, en plus, de veiller à ce que le paramètre qu'il transmet se trouve au final à la bonne place.

    Je suis désolé, mais ce genre de considération est de la responsabilité de la fonction appelée et non de l'utilisateur

    Citation Envoyé par germinolegrand Voir le message
    Ceci est exactement ce que je me suis tué à dire dans mes X derniers posts, et c'est exactement ce que j'applique, c'est la logique même -_-
    Et tu ne trouves pas aberrant de te payer le cout de la construction d'un unique_ptr et du transfert des responsabilités alors que tu as, d'une manière ou d'une autre, la possibilité de le donner directement à ce qui devra le gérer au final

    Je veux bien que ce n'est ni l'un ni l'autre qui te feront perdre énormément de perfs, mais cela revient à aller de Bruxelles à Paris en passant par Berlin: tu passes par des étapes supplémentaires qui n'ont d'autre but que la beauté du geste

    Sans compter que le message que tu fais passer à l'utilisateur est au final fort proche de "bah, écoutes, t'es trop con, ma classe prend la responsabilité du pointeur et si elle foires, ben tu n'auras qu'à recréer l'objet que tu lui files".

    Alors que, si tu attends le moment opportun (celui où tu es sur que "plus rien de grave" ne peut arriver et empêcher la fonction d'aller à son terme), pour effectivement transférer la responsabilité de la durée de vie de l'objet, hé bien, tu t'évites bien des problèmes :

    Si la classe foire, pour une raison ou une autre, la fonction appelante en sera la première avertie et pourra prendre toutes les mesures qu'elle jugera nécessaire, que ce soit reprendre la responsabilité de l'objet ou essayer de le transmettre "à autre chose" si elle n'en veut vraiment plus.
    Edit: voire pire, c'est ta logique préférée à la base, écrire l'interface indépendamment de l'implémentation...
    Et je le revendique!

    Mais comme tu le dis : indépendamment de l'implémentation.

    Même si tu estimes préférable d'utiliser un unique_ptr au lieu du shared_ptr qui est sensé le recevoir, le unique_ptr n'est qu'un "détail d'implémentation" de la fonction en question (encore plus que s'il s'agissait du membre de la classe à la limite).

    Cela se rapproche de n'importe quelle variable créée à l'occasion de l'exécution de ta fonction et qui n'a pas vocation à exister plus longtemps que jusqu'à la fin de la fonction.

    Et ne va pas me dire que c'est pareil avec les paramètres parce que le paramètre peut exister en dehors de la fonction, et que, en l'occurrence, il y aurait parfaitement d'éviter le recours à une variable anonyme temporaire pour passer celui-ci:

    On dispose exactement de ce qu'il faut, sans que cela ne change quoi que ce soit: le pointeur nu, au sujet duquel l'utilisateur doit prendre certaines disposition
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #35
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par koala01 Voir le message
    En forçant l'unique_ptr à exister pour qu'il soit passé en argument, ce sera la première chose qui sera détruite, et le pointeur sous-jacent avec, alors que, si tu reçois un pointeur nu, tant qu'il reste nu, celui qui a appelé la fonction peut encore toujours le récupérer "si quelque chose foire", sans que cela ne t'empêche d'utiliser le pointeur en cas de besoin, ni d'effectivement transférer la propriété de ce dernier au unique_ptr une fois que tout est fait.
    Donc selon toi, c’est mieux d’écrire :

    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
     
    // je mets volontairement un auto_ptr pour éviter la confusion avec un unique_ptr
    auto_ptr<A> a;
    B* b = f(); // f me renvoie un b dont j’ai la responsabilité. Je passe sur le cas où f n’est pas nothrow, ça va encore rajouter de la complexité…
    try
    {
      a = new A(b); // A peut renvoyer une exception.
    }
    catch(std::exception&)
    {
       // gérer l’exception et libérer ou réutiliser b
    }
    if(a) // a a été correctement construit)
    {
      // traiter
    }
    plutôt que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    // tiens, je n’ai plus besoin de mon auto_ptr
    try
    {
       A a(f()); // tiens, même si f n’est pas nothrow, ça ne change rien.
       // traitement
    }
    catch(std::exception&)
    {
      // gérer l’exception. Je sais que le résultat de f a été libéré quoiqu’il arrive, je n’ai pas à m’occuper de nettoyer ça.
    }
    Effectivement, le cas où :
    - la construction de l’objet B renvoyé par la fonction f est coûteuse
    - la construction de A a une chance raisonnablement élevée de merder

    n’est pas gérée au mieux par ce pattern. Mais dans ce cas, je pense que simplement déférer le transfert de responsabilité à une méthode nothrow et pas au constructeur règle la question.

    Et où espères tu le voir, ton move dans ta fonction l'utilisateur n'a pas à s'en inquiéter! Dans le code de l'utilisateur à condition qu'il ne décide pas d'appelerla fonction sous la forme de foo(std::unique_ptr<T>(monpointeur).
    Moi je suis con, je définis un contrat : si l’utilisateur ne veut pas respecter sa part du contrat, je me dédouane de toute responsabilité. Là, le mec a quand même déjà de bons outils qui devraient l’inciter à ne pas faire n’importe quoi. Je sais qu’un certain Médinoc a en signature une citation de Raymond Chen qui dit l’inverse, mais bon, on peut espérer une petite évolution de ce côté, non ?

    Et tu ne trouves pas aberrant de te payer le cout de la construction d'un unique_ptr et du transfert des responsabilités alors que tu as, d'une manière ou d'une autre, la possibilité de le donner directement à ce qui devra le gérer au final
    À ce tarif là, tu n’utilises plus std::string ou std::vector parce que le surcoût est du même ordre de grandeur.

    Alors que, si tu attends le moment opportun (celui où tu es sur que "plus rien de grave" ne peut arriver et empêcher la fonction d'aller à son terme), pour effectivement transférer la responsabilité de la durée de vie de l'objet, hé bien, tu t'évites bien des problèmes
    ça revient à la méthode nothrow dont je parle plus haut. Ça peut être adapté dans certains cas.

    Même si tu estimes préférable d'utiliser un unique_ptr au lieu du shared_ptr qui est sensé le recevoir, le unique_ptr n'est qu'un "détail d'implémentation" de la fonction en question (encore plus que s'il s'agissait du membre de la classe à la limite).
    Je ne peux pas te laisser dire ça. unique_ptr dans l’interface veut dire « je prends la responsabilité entière et unique de l’objet » tandis que shared_ptr veut dire « je partage avec toi la responsabilité de l’objet ». C’est vraiment deux notions différentes, c’est pas du tout du détail, c’est une composante importante du contrat.

  16. #36
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Je suis avec white_tentacle ici. unique_ptr<> en paramètre est fait partie de l'interface, mais peut n'avoir rien à voir avec l'implémentation, qui pourrait par exemple passer le pointeur (et son ownership) à une bibliothèque legacy don't l'interface est en C.

    unique_ptr en paramètre n'expose rien de l'implémentation, seulement le contrat que l'utilisateur n'est plus responsable de gérer la durée de vie de l'objet.

    Et cela n'est pas "exposer l'implémentation", c'est quelque chose d'aussi nécessaire qu'une garantie qu'une fonction ne modifie pas un autre objet, ou une garantie de thread-safety...
    Citation Envoyé par white_tentacle
    Citation Envoyé par koala01
    Et où espères tu le voir, ton move?
    Je pense que le move peut être invisible dans des cas où une fonction retourne déjà un unique_ptr rvalue:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    obj.SetFoo(CreateFoo()); //Pas de std::move
    mais dans ce cas précis je ne pense pas que ce soit un problème.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  17. #37
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Non, ce que je veux dire, c'est qu'un code proche de
    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
    18
    19
    20
    21
    class LaClasseQuRisqueDeFoirer{
        public:
            LaClasseQuRisqueDeFoirer(A* ptr){
                /* Je fais d'abord tout ce qui peut lancer une exception */
                /* puis, seulement, je donnes ptr à mon unique_ptr */
                uptr.reset(ptr); 
            }
     
        private:
            std::unique_ptr<A> uptr;
    };
    void foo(){
       /* on se fout d'ou il vient, mais ptr pointe bel et bien sur un objet correct */
        try{
            LaClasseQuRisqueDeFoirer obj(ptr);
        }catch(...){
            /* oups... la classe a foiré, par chance, j'ai toujours mon pointeur.
             * Que puis-je faire d'autre avant d'abandonner ?
             */
        }
    }
    sera quand même beaucoup plus cohérent qu'un code proche de
    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
    18
    19
    class LaClasseQuRisqueDeFoirer{
        public:
            LaClasseQuRisqueDeFoirer(std::unique_ptr<A> uptr):uptr(std::move(uptr)){ 
                 /* tout un tas de choses qui risquent de foirer */
            }
     
        private:
            std::unique_ptr<A> uptr;
    };
    void foo(){
        A* ptr=laFonctionQuiEnAppelleDixAutresPourCreerLePointeur();
        try{
            LaClasseQuRisqueDeFoirer obj(std::unique_ptr<A>(ptr));
        }catch(...){
            /* Merde, maintenant, ptr est détruit !!!
             * Je fais comment pour récupérer le coup ???
             */
        }
    }
    Toutes choses étant égales, on se fout pas mal de l'origine exacte du pointeur, évidemment.

    Ce qui compte, c'est que dans le premier cas, tu peux récupérer le pointeur en sachant qu'il est toujours valide et donc envisager une "autre solution" (quelle qu'elle soit :sauvegarder les données en catastrophe par exemple, avant de laisser planter l'application) .

    Alors que, dans le second cas, on se doute bien que tu voulais sans doute te débarrasser de la responsabilité de ptr, mais ce n'est pas forcément pour cela que tu voulais qu'il soit détruit si vite!

    Et la seule solution qu'il te restera pour récupérer un pointeur vers un objet strictement équivalent sera de recréer l'objet et de rejouer "toute la scène" qui a fait qu'il se trouvait dans son état bien particulier... Si tant est que ce soit possible (et ca l'est rarement).

    Autrement dit, si il y a effectivement "quelque chose qui foire", tu as peut être tout intérêt à laisser l'application planter car ce sera toujours mieux que d'avoir des données incohérentes, mais tu n'a, a priori, aucun moyen de "rattraper le coup" . Et il peut y avoir des tonnes de raisons pour que ce soit une soluton inacceptable!
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  18. #38
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    unique_ptr en paramètre n'expose rien de l'implémentation, seulement le contrat que l'utilisateur n'est plus responsable de gérer la durée de vie de l'objet.
    Ce contrat, il serait tout aussi bien écrit en toutes lettres dans la section précondition du cartouche de ta fonction ou de ta classe. Il y serait, selon moi, sans doute encore mieux

    Et cela n'est pas "exposer l'implémentation", c'est quelque chose d'aussi nécessaire qu'une garantie qu'une fonction ne modifie pas un autre objet, ou une garantie de thread-safety...
    Une garantie des pires emmerdes si "quelque chose vient à foirer", surtout
    Je pense que le move peut être invisible dans des cas où une fonction retourne déjà un unique_ptr rvalue:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    obj.SetFoo(CreateFoo()); //Pas de std::move
    mais dans ce cas précis je ne pense pas que ce soit un problème.
    Le move n'est de toutes manières pas le problème principal
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  19. #39
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    À ce tarif là, tu n’utilises plus std::string ou std::vector parce que le surcoût est du même ordre de grandeur.
    Le tarif est peut etre le même, mais le taux de change est totalement différent:

    D'un coté, tu as un cout, mais tu gagne énormément en sécurité d'utilisation,

    De l'autre coté, tu as un cout, mais aucun gain de sécurité à l'utilisation (on est même plutot proche d'une perte de sécurité, si on n'a pas la certitude que rien ne viendra lancer une exception dans la fonction)
    [quote]
    ça revient à la méthode nothrow dont je parle plus haut. Ça peut être adapté dans certains cas. En gros, appliquer un emplâtre sur une jambe de bois!

    Tant que ton pointeur est nu, il n'y a pas de problème : tu remontes la pile d'appels suite à l'exception et, du moment que tu as encore accès au pointeur, tu pourras encore essayer de trouver une solution à peu près propre.

    Mais, une fois qu'il émarge à un unique_ptr, il est foutu s'il arrive quoi que ce soit
    Je ne peux pas te laisser dire ça. unique_ptr dans l’interface veut dire « je prends la responsabilité entière et unique de l’objet » tandis que shared_ptr veut dire « je partage avec toi la responsabilité de l’objet ». C’est vraiment deux notions différentes, c’est pas du tout du détail, c’est une composante importante du contrat.
    Oui "je te prends l'entière et seule responsabilité du pointeur pour aller le partager avec d'autres" et alors qu'il a peut être déjà été partagé par ailleurs!

    C'est une logique implacable
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  20. #40
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    S'il est partagé, alors ce n'est pas un unique_ptr, et ça ne devrait pas être un pointeur nu non plus.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. Problème avec la sémantique de "transient"
    Par professeur shadoko dans le forum Langage
    Réponses: 10
    Dernier message: 06/06/2014, 15h24
  2. Collisions avec un objet en mouvement
    Par xoux28 dans le forum Tkinter
    Réponses: 15
    Dernier message: 30/03/2014, 11h49
  3. Réponses: 1
    Dernier message: 14/05/2012, 18h54
  4. ouverture avec une interpolation de mouvement d'une fenetre
    Par escteban dans le forum Général JavaScript
    Réponses: 7
    Dernier message: 19/06/2007, 17h04

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