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. #41
    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
    Le principe est justement d'imposer au fournisseur qu'il lègue l'entière responsabilité à la fonction qui le prend et en fait ce qu'elle veut, partager avec des composants internes si ça lui chante mais ça ne regarde en aucun cas celui qui appelle la fonction donc je ne vois pas ce que ça vient faire là.

  2. #42
    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
    J'ai l'impression que deux sujets sont discutés ici:
    • Les mérites et inconvénients de signaler qu'une classe attend un transfert de responsabilité par un paramètre unique_ptr<> (discuté par tout le monde)
    • Le fait qu' "imposer" un transfert de responsabilité soit en premier lieu une mauvaise chose (discuté dans le vide par Koala01)
    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.

  3. #43
    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
    Citation Envoyé par koala01 Voir le message
    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
    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!
    En fait, tu peux faire la même chose en prenant ton unique_ptr par rvalue reference. C'est ce qui était dit sur le post de stackoverflow:
    Therefore, my recommendation is this:
    • If you mean for a function to claim ownership of a unique_ptr, take it by value.
    • If you mean for a function to simply use the unique_ptr for the duration of that function's execution, take it by const&. Alternatively, pass a & or const& to the actual type pointed to, rather than using a unique_ptr.
    • If a function may or may not claim ownership (depending on internal code paths), then take it by &&. But I strongly advise against doing this whenever possible.
    (emphase mienne)

    Avec ça, le débat n'est pas "unique_ptr ou pointeur nu" mais "unique_ptr ou unique_ptr&&". Dans aucun des cas le pointeur nu n'est recommandé.
    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.

  4. #44
    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
    Petite parenthèse sur les exceptions, "le problème" (objet d'origine "perdu") que souligne Koala disparaît si vous utilisez une rref et pas une copie.

    Et je ne vois toujours pas ce qu'on gagne à le prendre par valeur, à par le coût d'une copie (bien qu'elle sera ellidée dans bien des cas).

  5. #45
    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 Flob90 Voir le message
    Petite parenthèse sur les exceptions, "le problème" (objet d'origine "perdu") que souligne Koala disparaît si vous utilisez une rref et pas une copie.

    Et je ne vois toujours pas ce qu'on gagne à le prendre par valeur, à par le coût d'une copie (bien qu'elle sera ellidée dans bien des cas).
    Cf le message du dessus de Médinoc : le contrat n’est pas le même entre la copie et la rref.

    Et comme la copie offre des garanties (dans le sens de postconditions) plus fortes, j’ai tendances à penser que ce doit être le défaut, la rref étant réservées aux cas où l’on ne peut pas garantir la prise d’ownership (postconditions plus lâches)

  6. #46
    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
    @white_tentacle: Oui, j'ai lu les arguments (qui avaient d'ailleurs été donné dans la première page, et j'ai aussi lu le message SO correspondant), mais ils ne me convainquent pas. D'autre part, c'est les arguments justifiant ces "contrats" qui intéresse. Parce que dire, "les contrats c'est ça", c'est pas une justification

    Ecrire move c'est dire [AMA] : je n'ai plus besoin de ceci si la fonction réussi. Je n'ai pas à espérer ni à me préoccuper de savoir si l'objet a ou n'a plus le même état qu'avant. Ça n'a aucune influence que la prochaine action soit un réaffectation (ou une RaZ) qu'une sortie de portée :
    • Si il a le même état, alors celui-ci est détruit, et alors ? J'ai bien dit que j'en avais plus besoin, la fonction à juger ne pas en avoir besoin, plus personne n'en veut, bon débarras (il l'aurait aussi été avec une copie, là c'est juste un peu plus tard).
    • L'état est vide, c'est alors la fonction qui a pris cet état car elle estimait en avoir besoin. Je peux me resservir sans problème de l'élément vide ou le détruire.


    Si la fonction ne réussi pas, c'est à l'implémentation de s'assurer de redonner le bon état (résistance forte).

  7. #47
    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
    Ok, je vais essayer de reformuler alors.

    Je trouve que plus les postconditions d’une fonction (à service rendu égal) sont fortes, mieux c’est. L’idée, c’est qu’avec des postconditions plus fortes, l’appelant a moins de préoccupations. Du coup, par défaut, je privilégie toujours l’approches qui me donne les postconditions les plus fortes.

    Le fait qu’ici, tu puisses t’asseoir sur tes préoccupations (car unique_ptr est suffisamment intelligent pour fournir un traitement par défaut acceptable)… n’est pas à mon sens un argument suffisant pour dire « tant qu’à faire, utilisons tout le temps celui que peut tout car qui peut le plus peut le moins ».

    Je trouve qu’utiliser l’outil/syntaxe qui documente le mieux le contrat est meilleur que d’utiliser celui qui peut tout faire. Si ta fonction garantit qu’elle prend l’ownership, autant que sa signature le dise explicitement, non ? Si au contraire, elle peut ne pas le prendre, autant là-aussi que sa signature soit différente de la première, cela n’en sera que plus explicite à la lecture.

    Et je sais que je ne pourrais (≠ devrais) me préoccuper des cas où l’ownership n’est pas transféré que dans les seconds cas.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Ok, je vais essayer de reformuler alors.

    Je trouve que plus les postconditions d’une fonction (à service rendu égal) sont fortes, mieux c’est. L’idée, c’est qu’avec des postconditions plus fortes, l’appelant a moins de préoccupations. Du coup, par défaut, je privilégie toujours l’approches qui me donne les postconditions les plus fortes.
    Ou bien tu parles effectivement de pré condition, ou bien ce que tu dis n'a strictement aucun sens :

    Les post conditions, c'est ce que tu garantis par contrat à l'utilisateur.

    La classe unique_ptr garanti par contrat à l'utilisateur que l'objet dont elle a la charge est détruit en même temps qu'elle.

    Mais les postconditions n'ont strictement rien à foutre dans les arguments de la fonction, surtout si elles se mettent à mentir à l'utilisateur en lui faisant croire que la fonction, la classe prend seule la responsabilité d'un objet alors qu'elle la prend de manière collégiale.

    Et si tu parles de préconditions, ma foi, il prend éventuellement sens que cela apparaisse au niveau des paramètres.

    Si ce n'est :

    Que comme tu ne peux, de toutes manières, absolument pas vérifier si ces préconditions sont remplies (il est impossible de faire un assert ou un static_assert sur ton pointeur pour t'assurer qu'il n'est, effectivement, sous la responsabilité de personne!) ou non, met cela dans la doc, dans le cartouche, écris le en grand sur les murs et les bureaux si tu veux, mais ne vas pas emmerder l'utilisateur à utiliser un unique_ptr qui, de plus, représente une condition plus forte que celle à laquelle ta classe ou ta fonction est contrainte!
    Je trouve qu’utiliser l’outil/syntaxe qui documente le mieux le contrat est meilleur que d’utiliser celui qui peut tout faire. Si ta fonction garantit qu’elle prend l’ownership, autant que sa signature le dise explicitement, non ? Si au contraire, elle peut ne pas le prendre, autant là-aussi que sa signature soit différente de la première, cela n’en sera que plus explicite à la lecture.
    Non, si c'est une post condition, ca doit apparaitre dans le nom de la classe ou de la fonction, dans la doc, mais en rien dans la signature!

    Si c'est une classe, appelles-la SingleOwnershipHolder, et si ce n'est pas dans le constructeur mais que c'est une fonction, appelle la giveMeOwnership ou takeOwnership (à mettre en opposition avec une fonction nommé releaseOwnership qui permet justement de défaire la classe de sa responsabilité) si tu veux, mais comme tu n'as de toutes façons aucun moyen de vérifier que l'utilisateur aura effectivement pris les dispositions pour que cela puisse se faire, ne vas pas l'emmerder avec ca... Encore une fois, surtout si c'est pour que la responsabilité soit partagée au final
    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

  9. #49
    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
    Non, si c'est une post condition, ca doit apparaitre dans le nom de la classe ou de la fonction, dans la doc, mais en rien dans la signature!
    Je ne réagis qu’à ça, tout le reste en découle : const est une postcondition, et apparait dans la signature. De toute façon, tout le contrat fait partie de la signature (ou inversement, la signature est une composante du contrat qui est en réalité ce qui définit l’interface de ma fonction, mais comme la signature est le seul endroit où on peut exprimer et vérifier statiquement du contrat dans le code, plus on peut en mettre dedans, mieux c’est).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je ne réagis qu’à ça, tout le reste en découle : const est une postcondition, et apparait dans la signature.
    Et non, const n'est pas une postcondition...

    C'est un invariant!

    Tu en veux la preuve ce n'est pas seulement quelque chose qui est vérifié lors de l'appel, c'est quelque chose qui a cours tout au long de l'exécution de la fonction
    De toute façon, tout le contrat fait partie de la signature
    Une partie du contrat (les invariants) transparait dans la signature, une autre (les postconditions, ou du moins une partie, et quand c'est possible) dans le nom, quand au préconditions... elle prennent place dans le code, mais pas forcément dans le code de la fonction
    (ou inversement, la signature est une composante du contrat qui est en réalité ce qui définit l’interface de ma fonction,
    La signature n'est en effet qu'une composante du contrat: elle représente les invariants qui auront cours tout au long de l'exécution de la fonction:
    Le type des paramètres attendus, leur nombre, leurs noms, la manière dont tu t'attends à ce qu'ils soit transmis et la possibilité que tu auras (ou non) de les modifier .

    Tout cela (autrement dit, l'ensemble de la liste des arguments, ce ne sont que des invariants et non des préconditions

    mais comme la signature est le seul endroit où on peut exprimer et vérifier statiquement du contrat dans le code, plus on peut en mettre dedans, mieux c’est).
    Encore une fois, non, tu ne peux y vérifier que les invariants, vu que c'est la seule chose que tu y trouves.

    Avec tout ce que l'on a ajouté dans type_traits (dont par exemple enable_if et tous les isQuelqueChose que l'on y trouve ) et le static_assert, tu peux déjà tester un grand nombre de préconditions à la compilation, pour les autres, celles qui ont besoin de données effectives, tu ne sais le faire que dans le corps de la fonction, et à l'exécution .

    Et si tu veux vraiment que les préconditions et / ou les postconditions apparaissent effectivement dans la signature en plus de dans la doc ou dans le corps de la fonction, la seule solution qu'il te reste pour le faire, c'est au niveau du nom de la fonction.

    Malheureusement, cela n'a qu'une valeur informative pour l'utilisateur, car y a aucun moyen de le tester
    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

  11. #51
    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 704
    Points
    2 704
    Par défaut
    On peut considérer que unique_ptr est également un invariant : la fonction a en permanence la responsabilité exclusive sur l'argument qui lui est passé.

    Pour citer un sage :
    "c'est quelque chose qui a cours tout au long de l'exécution de la fonction"

    Par ailleurs, les notions d'invariant et de post-condition ne sont pas mutuellement exclusives : un invariant définit une post-condition (et plus).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par oodini Voir le message
    On peut considérer que unique_ptr est également un invariant : la fonction a en permanence la responsabilité exclusive sur l'argument qui lui est passé.
    De toutes manières, dés le moment où l'on parle de type, il est clair que l'on parle d'invariant parce que c'est, très clairement, quelque chose qui reste stable tout au long de l'exécution

    Mais si tu vas vers cet aspect, je te rappellerai justement la phrase
    Pour citer un sage :
    "c'est quelque chose qui a cours tout au long de l'exécution de la fonction"
    afin d'attirer ton attention sur le fait que, non, décidément, la responabilité exclusive ne peut pas être considérée comme un invariant pour cette fonction particulière.

    Car cette fonction particulière dont on parle, son but n'est pas forcément de faire quelque chose en disposant de la responsabilité exclusive du pointeur, mais bien... de prendre ou d'obtenir la responsabilité exclusive du pointeur.

    Car, au début de l'appel et jusqu'à ce qu'elle ait été effectivement déléguée à l'une des variables connues de la fonction, cette responsabilité échoit à "autre chose" (ou à rien du tout selon le cas).

    C'est donc quelque chose qui sera peut être vrai très tôt lors de l'exécution de la fonction mais qui ne sera, malgré tout, pas vrai dés le début de l'exécution (au moment où l'on arrive à l'adresse mémoire à laquelle elle commence, juste après avoir placé les informations dans la pile d'appel et les différents registres).

    Comme ce n'est pas quelque chose qui est vrai "dés le début et pour toute la durée" (comme le type d'un des paramètres requis peut l'être), et même si le temps nécessaire pour faire en sorte que ce soit le cas se compte en cycle d'horloge, on est, au mieux, face à une postcondition, et non face à un invariant.
    Par ailleurs, les notions d'invariant et de post-condition ne sont pas mutuellement exclusives : un invariant définit une post-condition (et plus).
    Ce n'est effectivement pas exclusif, mais l'invariant est inclusif par rapport aux deux autres :

    Tu peux avoir une précondition ou une postcondition qui n'est pas un invariant :
    1. un pointeur peut devoir ne pas être nul lors de l'appel de la fonction, mais peut le devenir en cours d'exécution (et le rester jusqu'à la fin), et c'est alors une précondition, sans invariant et sans postcondition
    2. un pointeur peut devoir ne pas être nul lors de l'appel et après l'appel mais pouvoir le devenir en cours d'exécution, pour autant qu'il ne le soit plus après l'appel, on a alors une précondition et une postcondition
    3. La responsabilité exclusive d'un pointeur peut échoir à "n'importe qui" (à rien du tout) et devoir échoir à quelque chose de spécifique après la fonction et c'est alors une postcondition, sans invariant et sans précondition, et enfin
    4. La responsabilité exclusive peut devoir échoir à quelque chose de spécifique au moment de l'appel, continuer à échoir à cette chose spécifique durant toute l'exécution de la fonction et échoir encore à cette chose spécifique après l'appel de la fonction, et l'on a alors affaire à un invariant qui, par définition peut très bien servir de précondition et de postcondition également.
    Toute la nuance est là

    Cela se résumes, si on y réfléchis à un battement de quelque cycles d'horloge du préprocesseur, mais une précondition doit être vraie au plus tard au moment de l'appel de la fonction, à cette ligne dans l'assembleur où le processeur commence à ranger toutes ses données afin d'appeler la fonction en question, parce qu'après, il n'y a plus moyen de préparer les choses pour que la précondition soit respectée lorsque le processeur arrive sur l'instruction qui l'enverra effectivement vers la fonction en question.

    Evidemment, plus la précondition peut être respectée longtemps avant l'appel de la fonction, mieux on ne s'en porte généralement

    Dans le même ordre d'idée, une postcondition est une condition qui doit être respectée au plus tard avant l'instruction du processeur qui mettra fin à la fonction, autrement, la fonction ne pourra plus faire en sorte que cette condition soit respectée.

    Evidemment, d'une certaine manière, plus la postcondition est respectée longtemps avant la fin de la fonction, mieux nous nous porterons généralement

    Mais ni la précondition ni la postcondition n'ont le moindre droit de regard sur ce qui se fait entre ces deux moments "critiques" que sont l'appel de la fonction et la fin de la fonction.

    La seule chose qui ait le moindre droit de regard sur tout ce qui se fait entre ces deux moment critiques, c'est l'invariant, ce qui en fait aussi bien une précondition qu'une post condition... mais en mieux.

    Et, tout comme la précondition d'un coté et la postcondition de l'autre, plus l'invariant sera respecté longtemps avant et longtemps après l'appel de la fonction, mieux nous aurons tendance à nous porter, et ce, d'autant plus que certains invariants auront tendance à s'étendre à toute la durée de vie utile de cetrains objets
    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

  13. #53
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    Avec l'unique_ptr tu peux fournir un deleter.

    Je ne sais plus s'il faut le définir un paramètre template particulier ou si on peux faire comme un shared_ptr (fournir un lambda comme deleter par exemple)

  14. #54
    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
    Mais ni la précondition ni la postcondition n'ont le moindre droit de regard sur ce qui se fait entre ces deux moments "critiques" que sont l'appel de la fonction et la fin de la fonction.
    Sur ce point : en général, on s’autorise aussi à casser l’invariant le temps d’un traitement, au sein d’une fonction (mais certainement pas en sortie, comme tu l’as dit).

    Bien sûr, ça veut dire qu’on n’est plus thread-safe (sauf à rendre le traitement qui casse l’invariant atomique), et qu’il faut faire particulièrement attention à l’exception safety (je dois quoi qu’il arrive respecter mon invariant à la sortie de ma fonction).

    De toute façon, la notion d’invariant sur une fonction (sauf à ce qu’elle ne soit pas stateless, évidemment) ne correspond pas à grand chose d’utile amha, c’est surtout au niveau des invariants de classe que ça devient important.

  15. #55
    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 white_tentacle Voir le message
    Ok, je vais essayer de reformuler alors.

    Je trouve que plus les postconditions d’une fonction (à service rendu égal) sont fortes, mieux c’est. L’idée, c’est qu’avec des postconditions plus fortes, l’appelant a moins de préoccupations. Du coup, par défaut, je privilégie toujours l’approches qui me donne les postconditions les plus fortes.

    Le fait qu’ici, tu puisses t’asseoir sur tes préoccupations (car unique_ptr est suffisamment intelligent pour fournir un traitement par défaut acceptable)… n’est pas à mon sens un argument suffisant pour dire « tant qu’à faire, utilisons tout le temps celui que peut tout car qui peut le plus peut le moins ».

    Je trouve qu’utiliser l’outil/syntaxe qui documente le mieux le contrat est meilleur que d’utiliser celui qui peut tout faire. Si ta fonction garantit qu’elle prend l’ownership, autant que sa signature le dise explicitement, non ? Si au contraire, elle peut ne pas le prendre, autant là-aussi que sa signature soit différente de la première, cela n’en sera que plus explicite à la lecture.

    Et je sais que je ne pourrais (≠ devrais) me préoccuper des cas où l’ownership n’est pas transféré que dans les seconds cas.
    Je comprends bien ce que tu veux dire, et j'ai été imprécis dans mon dernier post. Il y a un élément que j'avais mentionné dans mon tout premier message que je n'ai pas répété et qui a son importance.

    Si je comprends bien ce que tu dis (et ce qui est dit dans le message SO), ils envisagent 3 cas : prise d'ownership, non prise d'ownership et prise potentielle. Je n'en envisage que 2 : prise ou pas prise. Comme tu le dis, ceci définit une partie d'une contrat, un autre partie du contrat c'est les exceptions, dans votre système à 3 cas cette partie est corrélée à la première, dans mon cas elle est totalement orthogonal.

    Pour résumer, le système à 3 cas :
    • Prise : Copie
    • Non Prise : const &
    • Prise Potentiel : &&

    La présence d'exception dans le service, le cas majeur dès qu'on arrive dans du code métier [1], tombe invariablement dans la situation 3 [2]. Et je m'attarde pas trop sur l'aspect assez horrible que prend le code si on utilise un noexcept conditionnel qui doit donc aussi se retrouver sur le type [3].

    Dans le système à 2 cas que je considère :
    • Prise : &&
    • Non Prise : const &

    Et :
    • Exception : rien
    • Pas d'exception : noexcept

    C'est parfaitement orthogonal, la lecture du contrat sur la signature est clair et sans redondance. Le seul cas de non prise lors de l'utilisation de && est la situation d'exception (nécessaire pour la résistance forte).

    Alors oui on pourrait introduire un cas supplémentaire, mais quel intérêt ? Ça n'apporte rien au contrat [4], ça complexifie l'écriture pour les cas où c'est potentiellement utile (ie nothrow conditionnel) et ça introduit une copie (même si élidée).

    Après le fait que std::unique_ptr ai un comportement "sympa" qui fait que même si celui qui a écrit le service à choisit un && alors qu'il ne prend pas l'ownership (il aurait du donc choisir un const &) le résultat est cohérent, c'est comme tu le dis du bonus, mais je ne l'ai absolument pas considéré dans mon raisonnement.

    NB: C'est que la première partie de ce que je pense être une bonne facon de faire. Idéalement je rajouterais un Concept par dessus. [5]


    [1] Ca concerne aussi le code templaté, sauf si on impose l'absence d'exception dans les contrats des paramètres template : ce qui est inutilement rigide.

    [2] Si on envisage une résistance forte.

    [3] Et horrible c'est dans le meilleur des cas, si on rajoute l'idée d'utiliser un Concept par dessus, ça devient techniquement complexe.

    [4] Au contraire ça brise l'orthogonalité, je veux modifier le rapport aux exceptions, je me retrouve à modifier le spécificateur et les paramètres.

    [5] Il faudra attendre le prochain standard pour ca

  16. #56
    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
    À la relecture, je comprends beaucoup mieux ce que tu dis et… je n’ai pas vraiment de bons arguments (les deux raisonnements me semblent valables ).

    Il y a juste ce point :
    Le seul cas de non prise lors de l'utilisation de && est la situation d'exception (nécessaire pour la résistance forte).
    qui m’ennuie (un tout petit peu) parce que c’est une convention d’écriture de code, mais que rien ne vient le vérifier statiquement (étant vicieux, je peux envisager des cas où je ne prends pas l’ownership, mais de manière silencieuse).

    Enfin, je pense que les cas :
    La présence d'exception dans le service, le cas majeur dès qu'on arrive dans du code métier [1], tombe invariablement dans la situation 3 [2]
    doivent être restreints au maximum. Si l’objet dont on ne veut plus la responsabilité est si important qu’on veut effectivement s’assurer qu’il ne soit pas perdu en cas de problème, alors il me semble plus opportun que ce transfert s’effectue de manière noexcept (ce qui est embêtant pour le constructeur uniquement en fait).

  17. #57
    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 white_tentacle Voir le message
    À la relecture, je comprends beaucoup mieux ce que tu dis et… je n’ai pas vraiment de bons arguments (les deux raisonnements me semblent valables ).
    Oui, je suis d'accord qu'ils sont les deux valables, la seule différence sera l'orthogonalité entre l'indication sur l'ownership et sur les exceptions, qui est une vrai redondance [1] lorsque tu as un noexcept conditionnel et que tu veux un paramètre en accord avec (et avec un concept, ie paramètre template, c'est probablement pas mieux).

    Citation Envoyé par white_tentacle Voir le message
    Il y a juste ce point :


    qui m’ennuie (un tout petit peu) parce que c’est une convention d’écriture de code, mais que rien ne vient le vérifier statiquement (étant vicieux, je peux envisager des cas où je ne prends pas l’ownership, mais de manière silencieuse).
    Je ne suis pas certain de bien comprendre, je comprend 2 situations :
    • L'auteur de la fonction à choisit un && alors qu'il ne prend pas l'ownership, dans ce cas c'est lui qui a fait une erreur, le "bon choix" (ca reste juste un avis ) est const&
    • L'auteur est dans une situation où il peut ou pas y avoir prise sans que cella ne soit exceptionnel. Dans ce cas je pense que ceci devrait être inscrit dans la documentation et que le "bon choix" est (dans notre cas) std::unique_ptr<T>& et l'utilisation de release.

    Mais en effet l'identification de && avec la prise d'ownership [2] est une convention, cependant toutes les "bonnes habitudes" du langage, les écritures idiomatiques et autres, ne sont que des conventions ou pas loin [3].

    Citation Envoyé par white_tentacle Voir le message
    Enfin, je pense que les cas :


    doivent être restreints au maximum. Si l’objet dont on ne veut plus la responsabilité est si important qu’on veut effectivement s’assurer qu’il ne soit pas perdu en cas de problème, alors il me semble plus opportun que ce transfert s’effectue de manière noexcept (ce qui est embêtant pour le constructeur uniquement en fait).
    Bien entendu, je suis parfaitement d'accord d'essayer de favoriser les fonctions noexecpt (qu'il y ai transfère ou pas). Mais il est très difficile d'indiquer une fonction noexcept dès qu'on template par exemple : comment t'assurer que l'utilisateur ne va utiliser que des T ayant aucun risque de lancer dans tes fonctions ?

    C'est possible en découpant les fonctions en plusieurs : un ensemble qui ne fait que prendre la responsabilité [4] et une autre qui fera les opérations "à risque". Le problème dans ce cas, c'est que l'état intermédiaire que tu auras n'aura pas forcément une réel sémantique. Et ça induit d'autres problèmes :
    • Tu as donc deux étapes : le transfert, puis le reste. Le transfert réussi nécessairement, mais si le reste lance, tu fais comment si ta décision dans ce cas est de revenir "plus en arrière" et de redonner la responsabilité à l'appelant ? Tu ajoutes la fonction pour le faire : ie tu ajoutes une fonction qui n'a d'intérêt que dans un cas. [5]
    • Et dans tout les cas tu te retrouves avec deux fonctions qui vont nécessairement être appélé à la suite, et jamais seul [6], en tant qu'utilisateur je serais vraiment décue de devoir faire cà et dans l'incompréhension


    [1] Sinon ca serait orthogonal ou pas loin

    [2] Je me place principalement dans des couches métier du code. Si on descend sur des couches plus techniques, je n'exclu pas des utilisations plus orienté par la technique et les mécanismes du langage que par une quelconque sémantique. De la même manière que je n'associe pas de sémantique is-a à un héritage publique lorsque j'utilise boost.mpl, ca n'a pas d'intérêt ni même de sens.

    [3] L'association des sémantiques avec des opérateurs/héritages (par exemple), ne sont pas guidé uniquement par des besoins techniques : si je n'insère pas une classe à sémantique d'entité dans une hiérarchie, j'ai pas de problème technique (slicing).

    [4] Si la classe ayant un ownership, comme unique_ptr, est bien, ca sera noexcept

    [5] Et qui ne définit aucun service, c'est un pur artefact technique.

    [6] Si elles peuvent être appelé seul, c'est que l'état intermédiaire à un sens, et dans ce cas ces remarques ne s'appliquent pas.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Sur ce point : en général, on s’autorise aussi à casser l’invariant le temps d’un traitement, au sein d’une fonction (mais certainement pas en sortie, comme tu l’as dit).
    En fait, il faut se mettre d'accord sur la granularité que l'on utilisera pour tester l'invaraint.

    Prenons le cas d'un carré, dont tout le monde sais que l'invariant principale est d'avoir quatre coté égaux.

    Si tu as une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void foo(Carré & c){
        /* plein de choses ici */
        c.setSize(5);
        /* autre choses ici */
    }
    tu es parfaitement en droit de considérer que setSize est une opération atomique, non pas en terme de threa-safety, mais dans le cadre du flux d'exécution de foo.

    Et tant que c a bel et bien 4 cotés égaux avant setSize et qu'il les a bel et bien après setSize, tu peux parfaitement considérer que l'invariant est respecté au niveau de foo.

    Maintenant, si tu décides d'aller voir ce qui se passe au niveau de setSize, selon la manière dont la taille des différents cotés sont représenter, tu peux être en mesure de respecter l'invariant ou de n'assurer que les pré et post conditions :

    Si tu n'as qu'une valeur à changer sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void Carre::setSize(int size){
        cote = size;
    }
    l'invariant est respecté.

    De la même manière, il pourra l'être si tu travailles avec deux points sous la forme de (*)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void Carre::setSize(int size){
        bottomRight= Point(topLeft.x+size, topLeft.x+size);
    }
    Mais, si tu dois modifier deux valeurs séparément, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Carre::setSize(int size){
        width=size;
        height=size;
    }
    ou sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Carre::setSize(int size){
        bottomRight.x = topLeft.x+size;
        bottomRight.y= topLeft.x+size;
    }
    on se rend compte qu'il y a "un petit instant" de battement (entre les deux instructions) pendant lesquelles l'invariant est, effectivement, cassé.

    (*) note que, si l'on veut aller au bout des choses, on pourrait parfaitement considérer que l'invariant sera cassé entre le moment de l'initialisation de x et celui de l'initialisation de y dans le constructeur.

    Toute la question est donc de savoir si ces "quelques cycles d'horloges" au sein de fonctions qui sont appelées par la fonction dans laquelle tu vérifies l'invariant (la fonction foo d'origine) ont de l'importance ou non.

    Si tu n'es pas dans un contexte multi thread, ce moment de battement n'aura aucune incidence.

    Dans un contexte multi thread, si ton carré est utilisé par plusieurs threads, cela peut en avoir car un thread "concurrent" pourrait essayer de récupérer les informations pendant ce moment de battement
    De toute façon, la notion d’invariant sur une fonction (sauf à ce qu’elle ne soit pas stateless, évidemment) ne correspond pas à grand chose d’utile amha, c’est surtout au niveau des invariants de classe que ça devient important.
    Disons que les invariants sont des conditions que l'objet doit respecter en tout temps au sein de la fonction envisagée.

    Mais comme je l'ai fait valoir pour le mot clé const, cela peut très bien ne s'appliquer que pour une fonction particulière, sans que cela ne soit un invariant de type
    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. #59
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Excusez moi si je relance le sujet, mais je viens juste de "tilter" sur quelque chose...

    Quelque part, je sais que certains de mes détracteurs sont d'ardents utilisateurs de Qt ( je ne vise personne, dieux reconnaitra les siens ).

    Je suis quelque peu surpris qu'ils puissent à la fois considérer ce que j'exprime comme une hérésie et estimer dans le même temps que Qt ait un comportement cohérent.

    Car, au final, chaque fois que l'on fait un widget->setLayer, un xxx->addWidget ou que l'on appelle tout autre comportement similaire, on fait appel à une mécanique dont on sait parfaitement qu'elle aura pour conséquence de donner la responsabilité de la durée de vie de notre pointeur à l'objet au départ duquel on appelle le comportement.

    A charge pour l'utilisateur de s'assurer que le pointeur est bel et bien "libre de toute attache", au besoin, en invoquant un comportement spécifique au départ du "propriétaire légal" du moment.

    Ce comportement pourrait très bien correspondre (si ce n'est pas le cas avec le support de C++11) à l'utilisation, en interne, d'un std::unique_ptr, et "tout le monde sait" qu'une fois que la responsabilité d'un pointeur a été "donnée" à un élément de Qt, il n'y a plus à s'en inquiéter car il sera détruit en temps et en heure.

    Cette logique est tout à fait saine parce qu'elle permet de s'assurer que tous les enfants d'un élément de Qt seront détruits exactement quand ils devront l'être (quitte à ce que ce soit à la fin de l'application), et personne ne penserait à la remettre en question.

    Dés lors, je vous le demande : Pourquoi estimeriez vous qu'une logique qui consiste à cacher un (équivalent à) unique_ptr serait bonne pour Qt et ne le serait pas pour vos propres développements

    Il n'y a donc, à mon sens, aucune raison cohérente de vouloir absolument indiquer explicitement le fait que l'on travaille avec un unique_ptr dés le moment où l'on sait qu'il y a une règle globale du projet qui dit que, quoi qu'il advienne, il y a toujours un objet (quel qu'il soit) qui prend la responsabilité des pointeurs que vous créez, et ce, quelle que soit la manière dont cet objet s'y prendra.

    Bien sur, cela nécessite sans doute de revoir le règles générales du projet, mais, si vous essayez d'introduire du unique_ptr "par petits bouts" dans votre projet, vous finirez très rapidement par ne plus savoir si la responsabilité de la durée de vie de tel ou tel pointeur est prise en charge par une classe RAIIsante ou non à moins de commencer à passer tous les cas d'utilisation et tous les chemins possible en revue.

    Le résultat des courses serait simplement aberrant : les pointeurs intelligents (quels qu'ils soient) sont sensés vous faciliter la vie, mais si vous ne les intégrez pas de manière globale dans l'ensemble du projet / du module que vous développez, il ne feront que vous la rendre encore plus difficile car vous passerez votre temps à vous demander si vous devez ou non invoquer delete sur vos pointeurs.

    C'est, finalement, pour cette raison que je plaide pour une intégration silencieuse des pointeurs intelligents grâce à laquelle l'utilisateur d'une classe n'a pas à s'inquiéter de la "machinerie interne" dont la classe s'occupe de gérer les pointeurs dont elle a la charge et grâce à laquelle l'utilisateur n'a plus qu'à se demander si l'objet qu'il manipule est d'accord de se démettre de la charge d'un pointeur ou non
    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. #60
    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
    Dés lors, je vous le demande : Pourquoi estimeriez vous qu'une logique qui consiste à cacher un (équivalent à) unique_ptr serait bonne pour Qt et ne le serait pas pour vos propres développements?
    Mais qui a dit que cette logique était bonne pour Qt?

    L'interface de Qt est née avant C++11, donc avant la sémantique de mouvement "done right". C'est donc non pas par choix, mais par circonstance (pour ne pas dire nécessité) que Qt n'a pas d'unique_ptr<> ou équivalent potable dans son interface.

    Edit: De plus, tu sembles être revenu en arrière, au principe de "fuite d'info sur l'implémentation" que je croyais déjà démenti et loin derrière nous...
    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