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

C++ Discussion :

Résultat inattendu : exemple tres simple d'imbrication de std::function


Sujet :

C++

  1. #1
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut Résultat inattendu : exemple tres simple d'imbrication de std::function
    Bonjour,

    J'ai eu aujourd'hui un résultat surprenant...
    Voici un exemple minimal qui reproduit le comportement :
    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
    22
    23
    #include <iostream>
    #include <functional>
     
    struct test {
    	std::function<int()> fils;
     
    	test( std::function<int()> f ) : fils(f) {
    	}
     
    	test(  ) : fils([](){return 0;}) {
    	}
     
    	int operator() () {
    		return 1+fils();
    	}
    };
     
    int main(int argc, char const *argv[])
    {
    	std::cout << test(test(test()))() << std::endl;
     
    	return 0;
    }
    Le résultat affiché est 1....
    Pourquoi le résultat n'est pas 3 ???
    Le compilateur fait il une optimisation non légale ?

    Merci pour votre aide

    Edit : Je pensai avoir trouvé le bug.... mais en faite non... :^/

  2. #2
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Ca ne viendrait pas de la copie?

  3. #3
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut
    Citation Envoyé par ternel Voir le message
    Ca ne viendrait pas de la copie?
    Ho oui ! bien vue ! Il fait une copie plutôt que de convertir test en std::function.
    Merci =)

    Existe-t-il un moyen pour que la conversion soit prioritaire sur la copie ? Car si je rends la copie privée, alors je ne peux plus faire appelle a std::function<int()>( test() );

  4. #4
    Expert confirmé

    Avatar de dragonjoker59
    Homme Profil pro
    Software Developer
    Inscrit en
    Juin 2005
    Messages
    2 034
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Software Developer
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2005
    Messages : 2 034
    Billets dans le blog
    12
    Par défaut
    Mettre la copie privée, et ajouter un opérateur de conversion?
    Si vous ne trouvez plus rien, cherchez autre chose...

    Vous trouverez ici des tutoriels OpenGL moderne.
    Mon moteur 3D: Castor 3D, presque utilisable (venez participer, il y a de la place)!
    Un projet qui ne sert à rien, mais qu'il est joli (des fois) : ProceduralGenerator (Génération procédurale d'images, et post-processing).

  5. #5
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut
    Il y a un truc que je ne comprends pas dans le choix de la syntaxe du C++
    Pour quoi avoir choisi de spécifier une sémantique spéciale pour ClassA::ClassA(ClassA const & o) comme constructeur de recopie ??
    Ça enlève la possibilité de définir un constructeur classique qui prendrait en entrée un objet du même type que lui non ?

  6. #6
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 600
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 600
    Par défaut
    Bonjour,

    Un constructeur test( test t ) donnerai : pour créer une copie d'abord commencer par créer une copie que l'on reçoit en paramètre!!!! le constructeur de copie est forcément test( test const& ).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
       test()               // crée un objet temporaire de type test
       test()()             // appelle l'operator()() d'un temporaire de type test
       test::operator()     // désigne les operator() des objets de type test
       &test::operator()()  // est l'adresse de l'operator() qui ne reçoit pas de paramètre.
       test(test(test()))() // crée une copie de copie de copie d'un test et appelle son operator()()
    Rien dans cette dernière expression ne désigne test::operator() ou &test::operator()(), encore moins un std::function<int()> et donc a aucun moment on créera un test( std::function<int()> f ).
    Même en créant un operator std::function<int()>()const, l'expression continuera de désigner une copie de copie, car la non-conversion est toujours pré-envisagée avant une conversion.

    PS: rendre privé ou même détruire un constructeur ou une méthode n'empêchera jamais qu'il ne soit envisagé (si on tente de l'utiliser il y a erreur, mais jamais n'est envisagé un traitement substitutif à cause d'un protection.)

  7. #7
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut
    Merci pour ces explications dalfab

    Donc cela signifie qu'il est impossible de créer un constructeur qui aura comme argument un objet du même type que la classe en question...
    Car le C++ considérera que ce n'est pas un constructeur classique, mais juste la manière de copier les objets...

    Pour quoi ne pas avoir créé un mot clé spécifique pour redéfinir la manière de copier les objets ? ça ne choque que moi ?

  8. #8
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 600
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 600
    Par défaut
    Il existe bien une fonction spécifique pour copier un objet c'est l'operator=.
    Il existe d'autre moyens pour créer ou copier des objets, on fait ce que l'on veut, mais dans ton exemple, tu utilises test(...) qui correspond à la création d'un objet.
    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
    test t1;         // utilise le constructeur par défaut test::test()
    test t2 = t1;    // utilise le constructeur par copie test::test(test const&)
    // tu peux si ça te chante, le ré-écrire et y mettre ce que tu veux, mais ne pas faire 
    // la copie donnera un code pour le moins surprenant!
    test t3(t1);     // idem
    test t4{t1};     // idem
    test t5 = test(t1); // idem!
    t2 = t1;         // t2 existe déjà, on appelle test::operator=(test const&)
     
    extern void fct( test t );
    fct( t1 );  // La fonction doit recevoir un objet de type test qui doit être créé à partir de t1. Le constructeur par copie test::test( test const& ) est appelé pour produire l'objet t.
    fct( test(t1) ); // idem! C'est cela qui t'embarrasse mais le mot test correspond à créer un test!
     
    extern test gct();
    t2 = gct();   // on appelle test::operator=(test const&) pour copier le retour de gct()
    test gct() {
       return t1; // on doit retourner un objet test, seul moyen en créer un par test::test( test const& ) pour avoir une copie de t1 en retour.
    }

  9. #9
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    operator= ne copie pas un objet, il modifie un objet existant a partir d'un autre.
    l'instruction a = b; modifie a, mais a existait déjà.

    Le constructeur dit "de copie" est celui qui prend en argument un objet du même type;
    Il porte se nom parce que précisément quand on construit un objet d'un type en prenant comme original un autre objet du même type, on en fait une copie.

    Les cas rarissimes où ce n'est pas le cas, on doit le contourner.
    La syntaxe n'est absolument pas spéciale, c'est le surnom qu'on donne à ce constructeur précis qui l'est.
    Ca, et le fait que le compilateur en génère un automatiquement si on ne le fait pas soi-même.

    Dans le cas présent, je ne désactiverai pas la copie (qui est appelée si tu écris test t1(f); test t2 = t1;.
    Mais je passerai soit par un operateur de conversion et un static_cast, soit une fonction membre normale, par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::function<void(void)> as_function() const {return std::function<void(void)>(*this); }

  10. #10
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut
    Citation Envoyé par ternel Voir le message
    La syntaxe n'est absolument pas spéciale, c'est le surnom qu'on donne à ce constructeur précis qui l'est.
    Oui, pardon, ce n'est pas la syntaxe qui est spéciale, c'est la sémantique attachée à ce constructeur précis qui l'est (car c'est un cas particulier).
    On ne peut même pas redéfinir ce constructeur en faisant "comme si" c'est un constructeur classique, car par convention c'est un constructeur de copie, donc toutes les bibliothèques, les compilateurs et les utilisateurs utiliseront ce constructeur pour copier les objets...
    Ça me fait un peu penser au choix qui avait était fait pour le langage Fortant en disant que certain nom de variable ont des types associés (i, j, k, l, m, n au type integer) et si la première lettre des noms des variables commence par a-h ou o-z alors ce sont des real.
    Ça me semble être des fausses bonnes idées, car ça essaye de simplifier le langage à coups de cas particuliers (et c'est toujours pénible à un moment ou un autre les cas particuliers...).

    Citation Envoyé par ternel Voir le message
    Dans le cas présent, je ne désactiverai pas la copie (qui est appelée si tu écris test t1(f); test t2 = t1;.
    Mais je passerai soit par un opérateur de conversion et un static_cast, soit une fonction membre normale
    Oui, c'est effectivement les seules solutions que je vois

  11. #11
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 154
    Billets dans le blog
    4
    Par défaut
    En fait c'est ton appel qui est foireux, parce qu'il n'appelle pas du tout les opérateurs comme tu sembles le croire mais se contente de créer et copier 1 objet.
    Si tu veux effectivement appeler ton opérateur et voir le résultat de 3 il faudrait à priori faire std::cout << test(test(test()())())() << std::endl;
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  12. #12
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    Oui, pardon, ce n'est pas la syntaxe qui est spéciale, c'est la sémantique attachée à ce constructeur précis qui l'est (car c'est un cas particulier).
    Quelle autre sémantique pourrais-tu vouloir attendre de ce constructeur ? Quand tu lis:


    Tu t'attends à autre chose qu'avoir une copie de a1 dans a2 ? Bien au contraire, ce qui serait surprenant, c'est qu'il fasse autre chose qu'une copie ! Concrètement, cette sémantique n'est pas forcée, c'est une convention. Si tu décidais de ne pas la respecter, je pleure d'avance pour les gens qui vont maintenir ton code derrière toi...

  13. #13
    Membre éclairé Avatar de Ekinoks
    Profil pro
    Étudiant
    Inscrit en
    Novembre 2003
    Messages
    687
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2003
    Messages : 687
    Par défaut
    Citation Envoyé par Bousk Voir le message
    En fait c'est ton appel qui est foireux, parce qu'il n'appelle pas du tout les opérateurs comme tu sembles le croire, mais se contente de créer et copier 1 objet.
    Si tu veux effectivement appeler ton opérateur et voir le résultat de 3 il faudrait à priori faire std::cout << test(test(test()())())() << std::endl;
    Eh... non... Mais c'est ma faute, j'aurai dû utiliser des noms aux classes plutôt que d'utiliser Test pour comprendre ce que je veux coder.
    Revoici la même bizarrerie sur un cas un peu plus concret.

    Je veux deux classes boites (une avec des parenthèses et une avec des crochets) et une classe Obj.
    Et je veux pouvoir emboîter mes boites les une dans les autres.

    Voici une proposition pour le faire sans héritage :

    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
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    struct Obj {
     std::string _val;
     
     Obj(std::string val) : _val(val) {
     }
     
     std::string operator()() {
      return _val;
     }
    };
     
    struct BoiteA {
     std::function<std::string()> _inside;
     
     template <class T>
     BoiteA(T inside) : _inside( inside ) {
     }
     
     std::string operator()() {
      return " ( " + _inside() + " ) ";
     }
    };
     
    struct BoiteB {
     std::function<std::string()> _inside;
     
     template <class T>
     BoiteB(T inside) : _inside( inside ) {
     }
     
     std::string operator()() {
      return " [ " + _inside() + " ] ";
     }
    };
     
     
    int main(int argc, char const *argv[])
    {
     std::cout << BoiteA( BoiteB( BoiteA( Obj("kado") )))() << std::endl;
     
     std::cout << BoiteA( BoiteA( BoiteA( Obj("kado") )))() << std::endl;
     
     return 0;
    }
    Si on ne fait pas attention, on peut croire que ce code est correct.
    L'affichage de la première ligne donne bien le résultat attendu : ( [ ( kado ) ] )

    Mais en fait, un bug peut se produire lorsque l'on essaye de mettre deux fois le même type de boite l'une dans l'autre.
    Cela est dû au fait que le constructeur BoiteA::BoiteA(const BoiteA &o) correspond au constructeur de recopie.
    Comme ce constructeur est un cas particulier et a une sémantique différente des autres constructeurs, bha ça part en sucette...
    On a donc le constructeur par copie qui vient court-circuiter notre constructeur BoiteA::BoiteA(BoiteA inside).
    Ainsi le résultat de la seconde ligne est : ( kado )


    Citation Envoyé par jblecanard Voir le message
    Quelle autre sémantique pourrait-tu vouloir attendre de ce constructeur ? Quand tu lis:


    Tu t'attends à autre chose qu'avoir une copie de a1 dans a2 ?
    Oui, je m'attendrai à autre chose. Pour rester cohérente avec la sémantique classique des constructeurs, l'instruction A a2(a1); devrais être possible seulement si on a défini le constructeur qui prend un argument A et ne devrais pas forcement faire une recopie (comme les constructeurs classiques quoi...).
    Pour faire une recopie j'aurais introduit une nouvelle syntaxe pour ne pas rentrer en confie avec les constructeurs.
    Un truc du style :
    Et qui aurait pour conséquence d’appeler une méthode spéciale de copie du type A::operator<-(const A &a).


    Citation Envoyé par jblecanard Voir le message
    Si tu décidais de ne pas la respecter, je pleure d'avance pour les gens qui vont maintenir ton code derrière toi...
    Oui, on est bien d'accord que maintenant que le choix a était fait de considérer le constructeur A::A(const A &a) comme le constructeur appelé pour faire une recopie, on est obligé de faire avec

  14. #14
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 154
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 154
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Ekinoks Voir le message
    Si on ne fait pas attention, on peut croire que ce code est correct.
    L'affichage de la première ligne donne bien le résultat attendu : ( [ ( kado ) ] )

    Mais en fait, un bug peut se produire lorsque l'on essaye de mettre deux fois le même type de boite l'une dans l'autre.
    Cela est dû au fait que le constructeur BoiteA::BoiteA(const BoiteA &o) correspond au constructeur de recopie.
    C'est que ton premier objet est casté implicitement en string via l'opérateur correspondant pour pouvoir trouver un constructeur valable.
    Alors qu'avec un objet identique il appelle le constructeur par copie qui ne nécessite aucune conversion.

    Citation Envoyé par Ekinoks Voir le message
    Oui, je m'attendrai à autre chose. Pour rester cohérente avec la sémantique classique des constructeurs, l'instruction A a2(a1); devrais être possible seulement si on a défini le constructeur qui prend un argument A et ne devrais pas forcement faire une recopie (comme les constructeurs classiques quoi...).
    Et bien tu t'attendrais à du faux.
    En C++ il n'y a pas grand chose de base, mais toute classe possède un constructeur, destructeur, constructeur de copie et opérateur d'assignation. Maintenant il y a aussi constructeur par mouvement et assignation par mouvement. Qui sont créés de manière trivial par le compilateur si pas explicitement écrits.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Encodage] test simple avec résultat inattendu
    Par paragoge dans le forum Langage
    Réponses: 4
    Dernier message: 16/05/2010, 11h52
  2. prob tres simple, form, method get
    Par killy-kun dans le forum Balisage (X)HTML et validation W3C
    Réponses: 8
    Dernier message: 25/08/2005, 10h29
  3. Réponses: 6
    Dernier message: 27/04/2005, 15h46
  4. [Defi] Query SQL qui semble tres simple
    Par Wakko2k dans le forum Langage SQL
    Réponses: 7
    Dernier message: 15/04/2004, 10h01
  5. Bon je vais essayer d'être simple :
    Par fpouget dans le forum Langage SQL
    Réponses: 8
    Dernier message: 09/04/2003, 17h46

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