1. #1
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : mai 2007
    Messages : 11 517
    Points : 50 397
    Points
    50 397

    Par défaut Pointeur sur fonction membre de classe et callback

    Bonjour,

    Je suis entrain de réaliser un parser de langage et j'ai un problème que je n'arrive pas à résoudre de manière élégante ou sans warning.

    La définition du langage est dans une classe à part (appelons la CLangage).
    Le parser est aussi dans une classe à part (appelons la CParser).

    Les 2 classes (CLangage et CParser) sont indépendante l'une de l'autre (aucun héritage commun).

    CLangage "enregistre" dans CParser les fonctions que le Parser doit appeler lorsque les éléments de langages sont rencontrés.

    C'est à dire en pseudo code:
    Code Pseudocode CLangage : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CLangage::Register()
    {
       CParser::Register(this, Callback);
    }
     
    CLangage::Callback()
    {
       // fonction qui doit etre appelée par le parser
       ...
    }
    Code Pseudocode CParser : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    CParser::Register(This, Callback)
    {
       // enregistrement des parametres dans m_this et m_callback
       ...
    }
     
    CParser::Execute()
    {
       ...
       m_this->m_callback();
       ...
    }

    Voilà, dans l'idée, c'est assez simple mais je n'arrive pas à l'implémenter

    Comme CParser ne connait pas CLangage, les pointeurs this et callback sont enregistrés comme des void * (aie) et je passe par une fonction statique libre de la classe CLangage pour reconstruire mes pointeurs (2eme aie)

    Code Pseudocode CLangage : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CLangage::Register()
    {
       CParser::Register(static_libre, this, Callback);
    }
     
    void static_libre(This, Callback)
    {
       This->Callback();
    }
     
    CLangage::Callback()
    {
       // fonction qui doit etre appelée par le parser
       ...
    }
    Code Pseudocode CParser : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    CParser::Register(StaticLibre, This, Callback)
    {
       // enregistrement des parametres dans m_static_libre, m_this et m_callback
       ...
    }
     
    CParser::Execute()
    {
       ...
       m_static_libre(m_this, m_callback);
       ...
    }

    Question : est ce que cela parait assez élégant comme solution ou bien est ce que vous penseriez à une autre manière plus élégante et/ou simple pour le faire

    1er problème dans la fonction CLangage::Register(), j'ai du mal a transformer le pointeur de fonction membre que je reçoit en void *. Je recois un warning que je n'arrive pas à comprendre
    CLangage.cpp:326: avertissement : converting from 'PtrFct_t {aka void (CLangage::*)()}' to 'void*' [-Wpmf-conversions]
    Register(static_exec_callback, this, (void *)fct);
    ^
    PtrFct_t est un pointeur sur fonction membre de classe
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef void (CLangage:: * PtrFct_t)(void);
    2eme problème lors du rappel dans la fonction static libre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void CLangage::StaticLibre(void * This, void * MemberFct)
    {
    	CLangage * this_ptr = (CLangage *)This;
    	PtrFct_t fct = (PtrFct_t)MemberFct;
    	(this_ptr->*fct)();
    }
    Je prends un message d'erreur
    CLangage.cpp:332: erreur : invalid cast from type 'void*' to type 'PtrFct_t {aka void (CLangage::*)()}'
    PtrFct_t fct = (PtrFct_t)MemberFct;
    ^
    Merci de votre aide (et oui, je suis toujours vivant mais je suis nettement moins actif sur le forum car j'ai énormément de travail)
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    4 759
    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 : 4 759
    Points : 15 448
    Points
    15 448

    Par défaut

    Ta callback devrait être une fonction libre void(void), ce qui peut être nourri via une lambda comme [this](){this->function();}. Un moyen de la stoquer serait std::function<void(void)>.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  3. #3
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : mai 2007
    Messages : 11 517
    Points : 50 397
    Points
    50 397

    Par défaut

    Mon intuition m'avait déjà fait regardé vers les lambda, tu confirmes donc mon intuition mais c'est un concept que je ne maitrise pas (encore ).

    Je n'ai jamais pratiqué les lambda, et je ne suis pas sûr de comprendre ce que tu as voulu dire, tu pourrais préciser un peu ton idée ? Un bout de squelette de code serait le bienvenu :-)
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  4. #4
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    juin 2007
    Messages
    4 759
    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 : 4 759
    Points : 15 448
    Points
    15 448

    Par défaut

    Visiblement tu sais ce qu'est un pointeur de fonction.

    Une lambda, en tant que telle, est une valeur pour "une sorte de pointeur de fonction".
    C'est un moyen d'écrire une expression, dont la valeur est une instance d'un foncteur crée par le compilateur pour l'occasion.

    Par exemple, [&somme](int valeur) { somme += valeur; } est une expression dont la valeur est un bidule disposant d'un operateur () qui modifie somme en lui ajoutant son argument.

    Un peu de mise en oeuvre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void f() {
        std::vector<int> doubles{1,2,3};
        int somme = 0;
        std::for_each(doubles.begin(), double.end(), [&somme](int valeur) { somme += valeur; });
        std::cout << "somme des entiers = "<< somme << std::endl;
    }
    la partie entre crochets contient les "captures" qui sont des informations de contexte. Entre parenthèses sont les arguments à fournir à l'appel du foncteur, et entre accolades le code à effectuer.
    Ici, somme est capturée par référence, ce qui permet de la modifier.

    Ce code se comporte comme si tu avais écris:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void f() {
        std::vector<int> doubles{1,2,3};
        int somme = 0;
        struct lambda_de_f_1 {//ceci est bien une déclaration de structure locale
            int & somme;
            lambda_de_f_1(int & somme): somme(somme) {}
            operator()(int valeur) { somme += valeur; }
        };
        std::for_each(doubles.begin(), double.end(), lambda_de_f_1(somme) );
        std::cout << "somme des entiers = "<< somme << std::endl;
    }
    D'autre part, std::function est un type spécialement construit pour stocker un foncteur (ou un pointeur de fonction) appelable d'une certaine manière.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  5. #5
    Membre expérimenté
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 365
    Points : 1 591
    Points
    1 591

    Par défaut

    Bonjour,

    Citation Envoyé par ram-0000 Voir le message
    Comme CParser ne connait pas CLangage, les pointeurs this et callback sont enregistrés comme des void * (aie) et je passe par une fonction statique libre de la classe CLangage pour reconstruire mes pointeurs (2eme aie)
    Tu as raison de dire aïe, car il n'y a pas de garantie que de telles conversions fonctionnent. Cela dépendra du compilateur.
    Les pointeurs de fonction membre peuvent avoir plusieurs tailles différentes.
    Dans le cas simple où il n'y a ni héritage, ni fonctions virtuelles, le compilateur peut décider de ne stocker dans le pointeur de fonction membre que l'adresse de la fonction. Dans ce cas, une variable de type void* sera assez grande pour contenir cette valeur aussi.
    Mais, dans certains cas d'héritage multiple, un pointeur de fonction membre peut avoir besoin de contenir aussi un offset pour décaler le pointeur this lors de l'appel à la fonction pointée. Exemple dans Visual C++ : https://blogs.msdn.microsoft.com/old...09-00/?p=40713
    Et dans le cas où un pointeur de fonction membre peut pointer vers une fonction virtuelle, il aura besoin de pouvoir trouver le pointeur vers la table virtuelle et de connaître l'indice de la fonction à appeler dans cette table virtuelle.

  6. #6
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    26 374
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

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

    Informations forums :
    Inscription : septembre 2005
    Messages : 26 374
    Points : 37 837
    Points
    37 837

    Par défaut

    Question : est ce que cela parait assez élégant comme solution ou bien est ce que vous penseriez à une autre manière plus élégante et/ou simple pour le faire
    Alors, en général:
    • Avant C++11, le callback par fonction statique + pointeur de contexte était la bonne solution (et a le mérite d'être compatible avec une interface C, utile si tu veux décomposer ça en bibliothèques dynamiques).
    • Depuis C++11, func<ton prototype> est censé s'occuper de stocker pointeur et contexte (un tel objet étant construit avec bind() si je me souviens bien).


    En particulier pour ton code, le coup de convertir le pointeur de fonction en void* présente deux problèmes:
    • Le problème de taille de pointeurs signalé par d'autres
    • Le fait que même avant C++11, tu ne devrais pas avoir besoin de faire ça: Le prototype de ta fonction callback doit être fixe, ou tu ne pourras pas l'appeler de toute façon!
    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.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 457
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 457
    Points : 22 634
    Points
    22 634

    Par défaut

    Salut,

    Il semble normal que le langage ne connaisse pas le parser, peut être un peu moins que le parser ne connaisse pas le langage! A moins d'introduire une notion entre les deux: la notion de token. Le parser pourrait alors se "contenter" de repérer les "groupes" de caratctères reliés entre eux, et de travailler sur une hiérarchie de classe simplissime (voir, peut-être une union, d'ailleurs), composée (j'en oublie peut être)

    • d'un token général (classe parent de tous les autres
    • d'un token "littéral" (ou de plusieurs, pour pouvoir représenter une chaine de caractères littérale aussi bien qu'un entier ou qu'un réel
    • d'un token "identifiant" (tout ce qui est représenté par une chaine de caractères qui ne représente pas une valeur littérale )
    • d'un token "séparateur" (tout ce que l'on classe dans la catégorie "ponctuation")


    Le parser se contenterait donc de déterminer quel typ de token il doit envoyer et de la valeur qu'il doit lui donner (car il faudra bien qu'il en donne une a tous); à charge pour le langage de savoir à quoi correspondent quels identifiants et quels séparateurs ainsi que comment traiter les valeurs littérales

    Ceci dit, ce travail d'interprétation pourrait tout aussi bien être pris en charge par un lexer, spécifique à chaque langage, qui s'occupe de récupérer les tokens au niveau du parser, de déterminer ce qu'il font, et de générer un AST (qui utilise une notion d'expression et non la notion de token, si bien que cela peut être quelque chose de totalement différent) qui sera manipulé par le langage.

    L'un dans l'autre, le lexer pourrait entrer dans le moule du patron "mediator"
    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. #8
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : mai 2007
    Messages : 11 517
    Points : 50 397
    Points
    50 397

    Par défaut

    Ca y est, je viens de m'en sortir au prix d'une dépendance, c'est à dire que la classe langage connait la classe parser avec une notion de token.

    Pas facile d'utiliser (surtout de comprendre) std::function quand on vient du C pur et dur avec des pointeurs et des pointeurs de function

    Merci pour votre aide et vos retour
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

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

Discussions similaires

  1. pbm pointeurs sur fonction membre :)
    Par overbreak dans le forum C++
    Réponses: 8
    Dernier message: 15/02/2008, 15h14
  2. [POO] Pointeur sur fonction membre et héritage
    Par MrDuChnok dans le forum C++
    Réponses: 9
    Dernier message: 20/07/2006, 17h19
  3. Pointeur sur fonction membre avec parametre
    Par Glosialabolas dans le forum C++
    Réponses: 7
    Dernier message: 06/02/2006, 02h32
  4. Réponses: 10
    Dernier message: 03/02/2005, 13h09
  5. Réponses: 5
    Dernier message: 12/01/2005, 20h58

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