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 :

fonction en paramètre d'une autre fonction


Sujet :

Langage C++

  1. #1
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut fonction en paramètre d'une autre fonction
    Bonjour,

    Je suis entrein de coder un menu pour un jeu.

    j'ajoute des bouton grâce a une méthode :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    AddButton("button name");
    J'aimerai faire un lien entre ce bouton est une fonction pour que quand je clique dessus mon programme lance ma fonction.

    Exemple: menu.AddButton("Play"); sa me lance la fonction void Play(void);

    j'ai pensé a metre un deuxieme parametre a cette fonction et lui donner l'adresse de la fonction mais je sais pas si la méthode est bonne quel est votre avis ?

  2. #2
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 146
    Billets dans le blog
    4
    Par défaut
    Salut,

    oui c'est une façon de faire. Ce dont tu as besoin s'apelle une callback, ça peut être sous forme de foncteur, std::function ou pointeur de fonction (et peut-être un autre procédé que j'ai pas en tête, ceux-ci étant les principaux).
    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.

  3. #3
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut Problème à la compilation
    Game.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    menu.AddButton("Play",Game::Play);
    Menu.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ligne16  |   void AddButton(const char*,void (Game::*)());
    Menu.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void Menu::AddButton(const char* ptext,void (Game::*ptr_function)())
    Compilateur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    ||=== Build: Debug in THELAST (compiler: GNU GCC Compiler) ===|
    include\Menu.h|16|error: expected ')' before '::' token|
    include\Menu.h|16|error: expected ')' before '::' token|
    include\Menu.h|16|error: expected ';' at end of member declaration|
    include\Menu.h|16|error: expected id-expression before '*' token|
    ||=== Build failed: 4 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Salut,

    Utilises plutôt std::function, qui offre bien plus de souplesse que les pointeur de fonctions.

    Il faut en effet savoir que:
    1- les pointeurs sur des fonctions membres diffèrent en fonction de la classe à laquelle ils font référence: Avec deux classes exposant une fonction acceptant la même signature comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class Alpha{
    public:
        void foo(int i);
    };
    class Beta{
    public:
        void foo(int i);
    };
    les pointeurs de fonctions qui correspondent sont de typevoid (Aplaha::*)(int) pour la classe Alpha et void (Beta::*)(int) pour ... Beta

    2- les fonctions membres statiques de classe ne posent pas ** trop ** de problème, mais les fonctions membre non statiques de classes présente une particularité irritante: celle de se voir adjoindre un paramètre (premier) de manière implicite par le compilateur: le fameux pointeur this qui désigne l'instance de la classe au départ de laquelle la fonction a été appelée.

    En utilisant std::function, il est possible de fournir des fonctions libres, des fonctions membres statiques, des fonctions membres non statiques et même des expressions lambda. Observe:
    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
    #include <iostream>
    #include <functional>
    class Button{
    public:
        void add(std::string const & text, std::function<void()> function){
            std::cout<<text<<" called : ";
            function();
        }
    };
    void freeFunction(){
        std::cout<<"free function called\n";
    }
    struct StaticStruct{
        static void staticFunction(){
            std::cout<<"static function called\n";
        }
    };
    struct Alpha{
        void nonStatic(){
            std::cout<<"member function called\n";
        }
        void parameterFun(int i){
            std::cout<<"member function with "<<i<<" as parameter\n";
        }
    };
    int main(){
        Button b;
        b.add("free function ", freeFunction);
        b.add("static function", StaticStruct::staticFunction);
        Alpha a;
        b.add("non static", [&](){a.nonStatic();});
        b.add("lambda",[](){    std::cout<<"lambda expression called\n";});
        b.add("using bind ",std::bind(&Alpha::nonStatic,&a));
        b.add("using bind for param",std::bind(&Alpha::parameterFun,&a, 5));
        return 0;
    }
    qui, une fois compilé produira la sortie
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    philippe@philstower:~/Documents$ g++ test.cpp 
    philippe@philstower:~/Documents$ ./a.out 
    free function  called : free function called
    static function called : static function called
    non static called : member function called
    lambda called : lambda expression called
    using bind  called : member function called
    using bind for param called : member function with 5 as parameter
    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

  5. #5
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    J'avou que std::function ç l'air pas mal pour ce que je veux réaliser. Je vais voir ce que je peux faire.

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    A vrai dire, c'est plus que pas mal, c'est absolument génial!!!

    Le seul reproche que l'on pourrait faire à cette classe, c'est de n'être accessible qu'à partir de C++11. Mais bon, comme cela fait déjà 7 ans que cette version de la norme est sortie, et qu'il y a eu des évolutions supplémentaires au travers de C++14 et de C++17, on pourrait considérer comme criminel de ne pas encore avoir fait la transition (sauf cas très particulier, cela va de soi )

    Si cela t'intéresse, j'ai d'ailleurs mis au point un système de signaux et de slots en utilisant cette possibilité. En voici le code Signal.hpp

    Note que l'apparente longueur du code est essentiellement due aux commentaires présent afin de permettre à doxygen de générer la documentation, car, sans eux, cela correspondrait à moins de deux cents lignes de code
    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

  7. #7
    Invité
    Invité(e)
    Par défaut
    et sinon il y a libsigc++ : https://en.wikipedia.org/wiki/Libsigc%2B%2B
    c'est juste utilisé dans gtkmm depuis 20 ans

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par SimonDecoline Voir le message
    et sinon il y a libsigc++ : https://en.wikipedia.org/wiki/Libsigc%2B%2B
    c'est juste utilisé dans gtkmm depuis 20 ans
    Ouaip, mais celle que je présente est header only et composée d'un seul et unique fichier

    (bon, d'accord: j'ai des fichiers doxygen et des tests qui vont avec, ce qui fait que ce n'est pas tout à fait vrai, mais, pour l'utilisation, un seul fichier suffit )
    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. #9
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    Merci pour tout je suis parvenu à passer en paramètre d'une fonction une fonction grâce a std::function c'est parfait sauf que la je bloque sur un problème quand je veux passer la méthode d'une classe.

    J'aimerai que vous m'expliquez pourquoi ce code ne fonctionne pas svp.

    main.cpp
    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
     
    #include <iostream>
    #include "A.h"
     
    using namespace std;
     
    int main()
    {
        cout << "Test pointeur de fonction !" << endl;
        cout << "---------------------------" << endl;
     
        A a;
     
        a.Run();
     
        return 0;
    }
    A.h
    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
     
    #ifndef A_H
    #define A_H
    #include "B.h"
     
    using namespace std;
     
    class A
    {
        public:
            A();
            void Run();
            void Play();
            void Quit();
            virtual ~A();
     
        protected:
     
        private:
            bool loop;
            B b;
    };
     
    #endif // A_H
    A.cpp
    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
     
    #include "A.h"
     
    A::A()
    {
        //ctor
        loop = true;
    }
     
    void A::Run()
    {
        b.Lunch(&A::Play);
    }
     
    void A::Play()
    {
        cout << "Le jeu peux commencer !" << endl;
        cout << "-----------------------" << endl;
     
        Quit();
    }
     
    void A::Quit()
    {
        cout << "fin du jeu" << endl;
        cout << "----------" << endl;
     
        loop = false;
    }
     
    A::~A()
    {
        //dtor
    }
    B.h
    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
     
    #ifndef B_H
    #define B_H
    #include <functional>
     
    using namespace std;
     
    class B
    {
        public:
            B();
            void Lunch(function<void()> );
            virtual ~B();
     
        protected:
     
        private:
    };
     
    #endif // B_H
    B.cpp
    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
     
    #include "B.h"
     
    B::B()
    {
        //ctor
    }
     
    void B::Lunch(function<void()> fct)
    {
        fct();
    }
     
    B::~B()
    {
        //dtor
    }
    Compilateur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    ||=== Build: Debug in test (compiler: GNU GCC Compiler) ===|
    C:\Users\sophie\Desktop\test\src\A.cpp||In member function 'void A::Run()':|
    C:\Users\sophie\Desktop\test\src\A.cpp|11|error: no matching function for call to 'B::Lunch(void (A::*)())'|
    include\B.h|11|note: candidate: void B::Lunch(std::function<void*()>)|
    include\B.h|11|note:   no known conversion for argument 1 from 'void (A::*)()' to 'std::function<void*()>'|
    C:\Users\sophie\Desktop\test\src\A.cpp||In member function 'void A::Play()':|
    C:\Users\sophie\Desktop\test\src\A.cpp|16|error: 'cout' was not declared in this scope|
    C:\Users\sophie\Desktop\test\src\A.cpp|16|error: 'endl' was not declared in this scope|
    C:\Users\sophie\Desktop\test\src\A.cpp||In member function 'void A::Quit()':|
    C:\Users\sophie\Desktop\test\src\A.cpp|24|error: 'cout' was not declared in this scope|
    C:\Users\sophie\Desktop\test\src\A.cpp|24|error: 'endl' was not declared in this scope|
    ||=== Build failed: 5 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
    Merci pour votre aide.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Play est une fonction non statique de ta classe. Tu dois donc utiliser une expression lambda comme à la ligne 31 de l'exemple de ma première intervention ou std::bind comme à la ligne 33
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void A::Run()
    {
        b.Lunch([&](){Play();});
        /* OU - OU - OU */
       b.Lunch(std::bind(&A::Play, this));
    }
    PS : la directive using namespace std; est une directive issue d'un autre temps qui ne devrait plus être utilisée dans le code moderne, et, en tout état de cause, qui ne devrait JAMAIS être utilisée globalement dans un fichier d'en-tête.
    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. #11
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    Ok j'ai compris plus Jamais de using namespace std; c'est pour les vieux de plus je viens de comprendre à quoi sert une fonction lambda grâce à toi.

    En enlevant using namespace std; je suis tomber sur un autre problème.

    A.h
    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
     
    #ifndef A_H
    #define A_H
    #include "B.h"
    #include <iostream>
     
    class A
    {
        public:
            A();
            void Run();
            void Play();
            void Quit();
            virtual ~A();
     
        protected:
     
        private:
            bool loop;
            B b;
    };
     
    #endif // A_H
    Compilateur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    ||=== Build: Debug in test (compiler: GNU GCC Compiler) ===|
    include\A.h|19|error: 'B' does not name a type|
    ||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
    J'ai bien compris qu'il ne reconnais pas B comme le nom d'un type mais pourtant j'ai inclus B.h.
    Si tu peux m'éclaircir sur ce point stp.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Tiens, voilà qui est surprenant...

    Me confirmes tu
    1. que la seule modification que tu as apportée aux fichiers d'en-tête est d'avoir supprimé la directive using namespace std
    2. qu'il n'y a aucune autre erreur avant celle que tu pointes
    3. que les options de compilation indiquent bien le dossier dans lequel se trouva A.h et B.h comme dossier dans lequel le compilateur doit chercher après les fichier d'en-tête (avec Gcc, c'est l'option -I<dossier à utiliser> . Attention, c'est un i majuscule et non un L minuscule )
    4. que tes fichiers d'en-tête portent bien le nom de A (majuscule).h et de B(majuscule).h


    As tu essayé en remplaçant ta directive #include "B.h" par une directive #include <B.h> les deux sont normalement autorisées, mais il y a quelques subtiles différences entre les deux (en dehors de l'utilisation des guillemets à la place de < et de >, s'entend) , et j'utilise < et > par habitude

    NOTA: Tu n'as aucun besoin d'inclure <iostream> dans A.h, vu que tu n'utilise aucune des fonctionnalités auxquelles il donne accès
    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. #13
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    Pardon excuse moi. J'avais inclus A.h dans B.h et B.h dans A.h erreur de débutant (même si j'en suis un).

    Normalement <iostream> j'en ai besoin dans mon A.cpp j'utilise des std::cout.

    En tout cas mon programme de test marche correctement !

    Si je résume quand je fais b.Lunch([&](){Play();}); sa revient à dire que je passe l'adresse de la méthode Play() en paramètre de ma méthode Lunch.
    La std::function me permet de faire passer des méthodes en paramètres à d'autre méthodes.

    Je voulais savoir par la même occasion ton fichier sur les signaux quand et comment l'utiliser ? comme tu as remarquer je suis un débutant et je comprend pas tout encore.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par johny13400 Voir le message
    Pardon excuse moi. J'avais inclus A.h dans B.h et B.h dans A.h erreur de débutant (même si j'en suis un).
    Ben, il n'y a rien que je doive excuser, c'est toi qui est embêté, pas moi :D

    Et ce n'est surement pas une tare d'être débutant, nous sommes tous passés par là
    Citation Envoyé par johny13400 Voir le message
    Normalement <iostream> j'en ai besoin dans mon A.cpp j'utilise des std::cout.
    Ben, alors, inclus le dans... A.cpp et non dans A.h...

    La règle générale est de n'inclure dans chaque fichier que ce qui est strictement indispensable: tu n'as pas besoin de <iostream> dans A.h, mais tu en as besoin dans A.cpp :question: ben, tu l'inclus dans le fichier dans lequel tu en as vraiment besoin (*) ;)

    Citation Envoyé par johny13400 Voir le message
    Si je résume quand je fais b.Lunch([&](){Play();}); sa revient à dire que je passe l'adresse de la méthode Play() en paramètre de ma méthode Lunch.
    La std::function me permet de faire passer des méthodes paramètres à d'autre méthodes.
    On va faire simple (les explications complètes le seraient beaucoup moins) : oui, c'est ca ;)

    Citation Envoyé par johny13400 Voir le message
    Je voulais savoir par la même occasion ton fichier sur les signaux quand et comment l'utiliser ? comme tu as remarquer je suis un débutant et je comprend pas tout encore.
    1. tu t'assures qu'il se trouve dans un dossier dans lequel le compilateur ira chercher après les fichiers d'en-tête (le même dossier que A.h et B.h, ce sera parfait :D)
    2. tu inclues le fichier avec la directive #include "Signal.hpp"
    3. tu crées un signal (cela pourrait être une donnée membre de ta classe B) sous la forme de Tools::Signal<> sig;
    4. par facilité, tu crées un alias de type sur le slot du signal dans l'accessibilité de ta classe B sous la forme de using slot_type = typename Tools::Signal<>::slot_type
    5. par facilité, tu expose une fonction membre dans l'accessibilité publique de ta classe B qui appellera sig.connect sous la forme deTools::Connection onSignal(slot_type slot);
    6. tu crées une connexion (cela pourrait être une donnée membre de ta classe A) pour maintenir le lien entre le signal et le slot sous la forme deTools::Connection connection;
    7. dans le constructeur de la classe A, tu utilises le retour de B::onSignal pour initialiser la connexion.
    8. Quand tu veux émettre le signal, il suffit d'appeler sig();

    au final, tes fichiers ressembleraient sans doute à quelque chose comme
    A.h :
    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
     
    #ifndef A_H
    #define A_H
    #include "B.h"
    #include <iostream>
     
    class A
    {
        public:
            A();
            void Run();
            void Play();
            void Quit();
            virtual ~A();
     
        protected:
     
        private:
            bool loop;
            B b;
            Tools::Connection connection;
    };
     
    #endif // A_H
    B.h
    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
    #ifndef B_H
    #define B_H
    #include <Signal.hpp>
     
    using namespace std;
     
    class B
    {
        public:
            using slot_type  = typename Tools::Signal<>::slot_type:
            B();
            virtual ~B();
            Tools::Connection onSignal(slot_type slot);
            void Launch(){
                 sig();
            } 
        protected:
     
        private:
            Tools::Signal<> sig;
    };
     
    #endif // B_H
    A.cpp:
    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
    #include "A.h"
     
    A::A():connection{b.onSignal(std::bind( &A::Play, this)}
    {
        //ctor
        loop = true;
    }
     
    void A::Run()
    {
        b.Lunch(&A::Play);
    }
     
    void A::Play()
    {
        cout << "Le jeu peux commencer !" << endl;
        cout << "-----------------------" << endl;
     
        Quit();
    }
     
    void A::Quit()
    {
        cout << "fin du jeu" << endl;
        cout << "----------" << endl;
     
        loop = false;
    }
     
    A::~A()
    {
        //dtor
    }
    B.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include "B.h"
     
    B::B()
    {
        //ctor
    }
    Tools::Connection B::onSignal(slot_type slot){
        return sig.connect(slot)
    }
    B::~B()
    {
        //dtor
    }
    NOTA :
    1- lunch signifie repas... je présumes que tu voulais dire "launch" (lancer) ;)
    2- J'ai modifié le moins possible ton code pour te montrer comment t'en servir, mais le but d'un système de signaux et de slot est -- justement -- de permettre d'avoir des éléments les plus indépendants possible les uns des autres.

    Une bien meilleure approche serait d'avoir quelque chose ressemblant à
    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
     
    #include <Signal.hpp>
    #include <iostream>
    using signal_type = Tools::Signal<>;
    using slot_type = typename signal_type::slot_type;
     
    class SigHolder{
    public:
        Tools::Connection onSignalEmited(slot_type slot){
            return sig.connect(slot);
        }
        void doIt(){
            sig();
        }
    private:
       signal_type sig;
    };
    class Worker{
    public:
        Worker(std::function<Tools::Connection(slot_type)> fun):
            connection{fun([&](){play();})}{
        }
        void play(){
        std::cout<<"Worker::play() called\n";
        }
    private:
        Tools::Connection connection;
    };
    int main(){
        SigHolder holder;
        Worker work{[&](slot_type slot){return holder.onSignalEmited(slot);}};
        holder.doIt();
    }
    ou sous une forme qui pourrait être 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
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #include <Signal.hpp>
    #include <iostream>
    using signal_type = Tools::Signal<>;
    using slot_type = typename signal_type::slot_type;
     
    class SigHolder{
    public:
        Tools::Connection onSignalEmited(slot_type slot){
            return sig.connect(slot);
        }
        void doIt(){
            sig();
        }
    private:
       signal_type sig;
    };
    class Worker{
    public:
        Worker(){
        }
        void createConnection(std::function<Tools::Connection(slot_type)> fun){
            connection= fun(std::bind(&Worker::play,this));
        }
        void play(){
        std::cout<<"Worker::play() called\n";
        }
    private:
        Tools::Connection connection;
    };
    int main(){
        SigHolder holder;
        Worker work;
        work.createConnection(std::bind(&SigHolder::onSignalEmited,&holder,std::placeholders::_1));
        holder.doIt();
    }
    sous une forme 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
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include <Signal.hpp>
    #include <iostream>
    using signal_type = Tools::Signal<>;
    using slot_type = typename signal_type::slot_type;
     
    class SigHolder{
    public:
        Tools::Connection onSignalEmited(slot_type slot){
            return sig.connect(slot);
        }
        void doIt(){
            sig();
        }
    private:
       signal_type sig;
    };
    class Worker{
    public:
        Worker(SigHolder & holder):Connection{holder.onSignalEmited(std::bind(&Worker::play,this)}{
        }
        void play(){
        std::cout<<"Worker::play() called\n";
        }
    private:
        Tools::Connection connection;
    };
    int main(){
        SigHolder holder;
        Worker work{holder}
        holder.doIt();
    }
    ou bien d'autres encore...qui donneraient tous les deux un résultat totalement identique, à savoir
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    philippe@philstower:~/Documents$ ./a.out 
    Worker::play() called
    (j'ai tout mis dans un seul et meme fichier, mais tu peux bien sur tout séparer à ta guise ;)

    Et, bien sur, il est possible de trouver des cas encore bien plus complexes ;)

    (*)Comme tu es débutant, je vais passer sur le laïus concernant le fait d'utiliser explicitement std::cout ou std::cin dans une fonction membre de tes classes, mais, a priori, ce n'est pas la meilleure idée qui soit ;)
    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. #15
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut Réponse à un MP de Bktero
    Bktero m'a envoyé un MP, dont j'ai décidé de vous faire par ici, car il est en rapport avec cette discussion
    Citation Envoyé par Bktero
    Hello,

    Du coup j'ai voulu testé ta bibliothèque de signal/slot et j'ai galéré à afficher quelque chose

    En fait, je suis très surpris que ce code :
    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
    #include <iostream>
    #include "Signal.hpp"
     
    void ma_fonction() {
    	std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
     
    int main(int, char*[]) {
    	Tools::Signal signal;
     
    	auto connection = signal.connect([]() {
    		std::cout << "OK" << std::endl;
    	});
     
    	signal.connect([]() {
    		std::cout << "pas vu..." << std::endl;
    	});
     
    	signal();
    }
    n'affiche que "OK"...

    Pourrais-tu me dire de quel côté regarder pour comprendre ça ?

    @+
    Bktero
    Tu dois toujours récupérer la connexion renvoyée par la fonction connect. C'est le "troisième larron" qui maintient le lien entre le signal et le slot qui y est connecté: quand elle est détruite, le slot est automatiquement déconnecté.

    Cela peut sembler conceptuellement embêtant, mais c'est le seul moyen d'assurer la déconnexion automatique des fonctions membres non statiques sans imposer la restriction (bien plus embêtante encore) que le signal doit être détruit avant les slots qui y sont connectés.

    En effet, si je n'avais pas imposé ce "troisième larron" une situation 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
     
    struct Worker{
        Worker(Tools::Signal<> & sig){
              sig.connect(std::bind(&Worker::print, this));
        }
        void print(){
            std::cout<<"Worker::print() called\n";
        }
    };
    int main(){
        Tools::Signal<> sig;
        {
            Worker worker(sig);
       } // oupps... worker is destroyed here... Its "print" function
         // is unavaible and will result in a segfault
      sig();
      return 0;
    }
    aurait toujours été possible, et cela n'aurait pas été bon
    <EDIT>
    Grâce à l'ajout de ce "troisième larron", je n'ai plus vraiment de problème, car, si je veux connecter worker à mon signal, les règles de portée jouent en ma faveur:

    En effet, 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
     
    struct Worker{
        void print(){
            std::cout<<"Worker::print() called\n";
        }
    };
    int main(){
        Tools::Signal<> sig;
        {
            Worker worker;
       } 
       auto conn = sig.connect(std::bind(&Worker::print, &worker));
      sig();
      return 0;
    }
    ne compilera purement et simplement pas, sous prétexte que worker est inconnu au moment où j'essaye de créer la connexion.
    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
     
    struct Worker{
        void print(){
            std::cout<<"Worker::print() called\n";
        }
    };
    int main(){
        Tools::Signal<> sig;
        {
            Worker worker;
            auto conn = sig.connect(std::bind(&Worker::print, &worker));
       }  // conn is destroyed first.  related slot is removed from the signal
         // worker is destroyed second.
      sig(); // calls all connected slots, but worker.print() t is not one of them
      return 0;
    }
    compilera, mais, comme les règles de portées font que conn sera détruit juste avant worker, et que le slot sera donc déconnecté correctement.

    Enfin, une troisième possibilité serait d'ajouter la connexion directement dans la structure Worker, sous une forme qui pourrait être 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
    struct Worker{
        Worker(Tools::Signal<> & sig):conn{ sig.connect(std::bind(&Worker::print, this))}{
        }
        void print(){
            std::cout<<"Worker::print() called\n";
        }
        Tools::Connection conn
    };
    int main(){
        Tools::Signal<> sig;
        {
            Worker worker(sig);
       } // worker is destroyed here
         // its "conn" member too, of course
        // ==>worker.print() is disconnected from sig
      sig();  // calls all connected slots, but worker.print() t is not one of them
      return 0;
    }
    qui compilera, mais qui, encore une fois, ne provoquera pas d'appel à worker.print()
    </EDIT>
    De la même manière, un code proche de
    Ton code devrait donc ressembler à
    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
    #include <iostream>
    #include "Signal.hpp"
     
    void ma_fonction() {
    	std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
     
    int main(int, char*[]) {
    	Tools::Signal signal;
     
    	auto connection = signal.connect([]() {
    		std::cout << "OK" << std::endl;
    	});
     
    	auto other = signal.connect(ma_fonction); // et oui, pour une fonction libre ou une fonction membre statique, ca suffit ;)
     
    	signal();
    }
    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

  16. #16
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    Merci pour ton aide. Du coup pour le menu de mon jeu c'est parfait j'arrive lancer les méthodes liées à mes boutons.

    Pour les signaux je vais t'avouer que je n'ai toujours pas cerner la chose.
    Est-ce que sa m'aurait simplifier la vie d'utiliser ton hpp dans mon cas de boutons ?

    Du coup pour ajouté un bouton je fais :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    menu.AddButton("Play",[&](){ Play(); });

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Sans doute, parce que
    1. ton bouton a déjà une responsabilité précise : celle d'afficher "quelque chose" dans la vue
    2. tu rentres typiquement dans une logique dans laquelle on a affaire à ce que j’appellerais volontiers de la "communication transversale" (*)

    (*)une "petite" explication s'impose ici :

    Toute fonction n'a de l'intérêt que si elle est utilisée. Si elle est utilisée, cela signifie -- généralement -- qu'il y a une fonction appelante d'une part et une fonction appelée de l'autre, ce qui implique:
    • que la fonction appelante doit ... connaitre la fonction qu'elle doit appeler (ce qui occasionne malgré tout une certaine dépendance)
    • que les deux fonctions vont devoir communiquer
      • la fonction appelante devra (le cas échéant) transmettre des paramètres à la fonction appelée et
      • la fonction appelée devra (le cas échéant) renvoyer une "valeur de retour" à la fonction appelante

    Mais, ca, c'est le cas le plus simple, celui que je désignerais volontiers comme étant de la "communication verticale", ne serait-ce que parce que chaque appel de fonction fait... rajouter "un élément" (plusieurs en fait) sur la pile d'appels et que, chaque fois qu'une fonction s'achève, les éléments qui ont été rajoutés à la pile d'appel en sont correctement retirés.

    Or, le bouton, lui, il se fout pas mal de savoir si "quelque chose" réagit à ses changements d'état. Et c'est ce qui fait que l'on rentre dans une logique de "communication transversale". c'est parce que le changement d'état (et non pas le bouton!) doit peut-être signaler à une ou plusieurs choses -- combien exactement Aucune idée... à quoi exactement Aucune idée -- qu'il est survenu.

    Je ne sais pas si j'arrive à bien faire comprendre la différence entre ce que j'appelle la "communication verticale" et la "communication transversale" D'un coté, tu fais appel à une (ou à plusieurs) fonctions qui est (sont) clairement identifiée(s) alors que de l'autre, tu fais peut-être appel à une (ou des) fonction(s) dont tu ne connaît -- a priori -- ni l'origine ni le nombre

    Nous pourrions utiliser le patron de conception observer dans lequel le bouton prendrait le rôle "l'élément observé" et "ce qui contient la fonction à appeler" prendrait le rôle de "l'élément observateur" pour mettre cette communication "transversale" en place. Mais la technique impliquerait pas mal de conséquences que nous préférerions éviter, parmi lesquelles on peut citer:
    • le fait que le bouton dépendrait de tous les éléments "qui contiennent la fonction qu'il doit appeler"
    • le fait que "l'élément observateur" dépendrait du bouton, ne serait-ce parce qu'il faut bien qu'il soit en mesure de ... s'enregistrer auprès de lui, afin d'être tenu au courant de sa situation.
    • le fait que "l'élément observateur" dépendrait du bouton, pour être en mesure de se "désenregistrer" au plus tard lorsqu'il cessera d'exister (autrement, nous serions confrontés à la situation que j'explique à Bktero dans la réponse que je lui ai fait: lorsque le bouton voudra signaler à tous ses observateur que "quelque chose lui est arrivé" (par exemple : qu'on a cliqué dessus), le fait d'appeler une fonction membre sur un objet qui n'existe plus ne pourra avoir qu'une seule issue : une belle et très ennuyante erreur de segmentation
    • et surtout, le fait que cela oblige le bouton à prendre une responsabilité qu'il ne peut pas prendre (en vertu du SRP): celle de maintenir à jour la liste des observateurs qui sont enregistrés auprès de lui.

    Nous avons donc besoin de "quelque chose" (en réalité plusieurs concepts) qui nous permette non seulement de réduire ce couplage qui est dangereux (il n'est jamais bon d'avoir une classe A qui dépend d'une classe B alors que la classe B dépend de la classe A ) mais aussi (et j'oserais presque dire "surtout") qui soit en mesure de prendre la responsabilité que nous ne pouvions pas donner au bouton.

    De plus, le fait de fournir "quelques concepts" supplémentaires aura un avantage majeur pour la suite. Car, il faut bien te dire que je viens de "dérouler toute une logique" pour un problème bien spécifique : les boutons d'un menu. Mais je pourrais dérouler exactement la même logique pour tous les éléments qui viendront à "coté" des éléments du menu; pour n'importe quel type d'élément susceptible de faire partie de... la vue (de manière générale). Et le mieux , c'est que ces concepts ne serviront pas que dans le cadre bien particulier de la vue, mais qu'ils seront réutilisable "à l'envi" dans un nombre incalculable de situations

    Les concepts dont je parle, ce sont ceux qui entrent en jeu dans les systèmes de signaux et de slots. Et ce sont ceux que ma bibliothèque fournis justement. à savoir:
    • le concept de slot, qui représente les fonctions qui doivent être appelées
    • le concept de signal qui se charge de "maintenir à jour" la liste des slots qui devront être appelés dans une situation bien précise et
    • le concept de connexion, qui joue le rôle de "troisième laron de l'histoire" et qui s'occupe de maintenir le lien entre un slot bien particulier et le signal auquel il est connecté.


    Bon, maintenant que j'ai bien (et surtout longuement ) justifié ma réponse, je présume que le but de ta question était surtout que je t'expliques en quoi un système de signaux et de slots pourrait t'aider dans ton optique de créer un menu avec des boutons . Voici donc comment.

    Commençons donc par le bouton. Outre toutes les autres choses que tu pourrais vouloir lui faire faire (et elles sont sans doute nombreuses), un bouton devra pouvoir émettre un signal "clicked" qui signifiera littéralement "on a cliqué moi". Et bien, ton abstraction pourrait ressembler à
    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
    /* le fichier d'en-tete (button.h) */
    #ifndef BUTTON_H
    #define BUTTON_H
    #include <Signal.hpp>
    /* un alias de type "global" pour la facilité : */
    using ClickedButtonSlot = typename Tools::Signal<>::slot_type; 
     
    class Button{
    public:
       /* on passe tout ce dont je n'ai pas parlé, pour se limiter au stricte
       * minimum: il doit permettre à un slot de se connecter au signal émis
       */
        Tools::Connection onClicked(ClickedButtonSlot slot);
       /*il peut y avoir une fonction qui permet de forcer le bouton à emettre son signal ;) */
       void click();
    private:
        Tools::Signal<> clicked;
    };
    #endif // BUTTON_H
    /* et l'implémentation (bouton.cpp) */
    #include <button.h>
    Tools::Connection Button::onClicked(ClickedButtonSlot slot){
        return clicked.connect(slot);
    }
    void Button::click(){
        clicked();
    }
    Avoue que cela aurait pu être plus compliqué, hein?

    Ensuite, nous nous intéresserons à deux fonctionnalités qui peuvent être intéressées par ce qui se passe au niveau du menu : la classe Game qui exposera à l'utilisateur une fonction "run" (qui sera activée lorsque le joueur cliquera sur le bouton "jouer") et une autre fonction "pause" (qui devra être exécutée si le joueur clique sur le bouton..."pause") et la class... Inventory (qui devra être affichée si le joueur si le joueur clique sur le bouton "inventaire). Voici, en gros, à quoi elle devront ressembler:
    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
    /* la classe Game (dans game.h) */
    #ifndef GAME_H
    #define GAME_H
    #include <Signal.hpp>
    class Game{
    public:
        void run();
        void pause();
    private:
    friend class Application; // pour lui permettre d'accéder aux connexions sans se poser de questions ;)
                              // j'y viendrai plus tard ...
    bool paused{false};
    bool running{false};
    Tools::Connection runConnection;
    Tools::Connection pauseConnection;
    };
    #endif
    /* et l'implémentation (dans game.cpp) */
    #include <game.h>
    void Game::run(){
        running  = true; // le jeu commence
        /* et c'est parti !!! */
        while(running && paused == false){
            /* tout ce qu'il faudra faire pendant le jeu */
        }
    }
    void Game::pause(){
        paused = paused? false : true; /* une fois sur deux, cela passe à true, 
                                        * la fois d'après, ca passe à false
                                        */
    }
    Et voilà... J'aurais pu parler du bouton "exit" qui aurait forcé l'arret de la partie, et sans doute de bien d'autres encore, ce ne serait pas devenu beaucoup plus complexe

    La classe Inventory (si on la limite à ce qui nous intéresse pour l'instant) est presque encore plus simple, car elle prendrait la forme 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
    /*le fichier d'en-tête (inventory.h) */
    #ifndef INVENTORY_H
    #define INVENTORY_H
    #include <Signal.hpp>
    class Inventory{
    public:
        void show();
        /* ... */
    private:
    friend class Application; // pour lui permettre d'accéder aux connexions sans se poser de questions ;)
                             // j'y viendrai plus tard ...
    Tools::Connection showConnection;
    };
    #endif //INVENTORY_H
    /* et le fichier d'implémentation (inventory.cpp) */
    #include <inventory.h>
    void Inventory::show(){
       /* tout ce qui doit être fait pour afficher l'inventaire */
    };
    Et voilà... Avoue que ce n'est pas plus compliqué, hein?

    Nous avons donc maintenant des éléments qui vont utiliser trois boutons différents. Nous avons donc de bonnes raisons de mettre en place la notion de menu

    Je vais passer sur le fait que le menu en lui-même est un élément qui devrait réagir au click sur l'ensemble de la surface qu'il représente, tout comme je vais passer sur le fait qu'un menu devrait sans doute se trouver dans une "barre de menu" qui devrait --elle aussi -- sans doute réagir au clique sur la surface qu'elle utilise. Cela ne ferait que rendre les chose plus complexes (quoi que bien plus correct visuellement ) sans apporter réellement de faits nouveau, en dehors du fait que le clique sur la barre de menus devrait faire afficher le menu (et les boutons qu'il contient) sous lequel se trouve le bouton et que le clique sur l'espace occupé par un bouton particulier devrait occasionner l'émission du signal propre à ce bouton.

    Ce qui nous intéresse pour l'instant, c'est :
    1. de pouvoir ajouter les trois boutons dont on a besoin et
    2. de pouvoir pouvoir accéder aux boutons créés afin d'y connecter nos slots
    3. de faire en sorte que le menu maintienne à jour la liste des boutons (autrement, nous ne saurons pas y accéder ).
    4. de pouvoir afficher le menu
    5. de pouvoir cacher le menu

    Le deuxième point de cette liste est sans doute celui qui pourrait poser le plus de problème.

    Par facilité, on va dire que le menu va maintenir la liste des boutons créés dans une std::map<std::string, Button> allButtons; dans laquelle la clé correspondra au texte du bouton En production, nous ferions très certainement autrement, mais pour l'exemple, cela sera bien suffisant

    Voici donc à quoi ressemblerait ma classe Menu
    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
    /* dans le fichier d'en-tête (menu.h) */
    #ifndef  MENU_H
    #define MENU_H
    #include <button.h>
    #include <string>
    #include <map>
    class Menu{
    public:
        /* permet d'ajouter un bouton au menu */
        void add(std::string const & text);
        /* permet de connecter un slot à un bouton bien particulier */
        Tools::Connection connectTo(std::string const & buttonKey, ClickedButtonSlot slot);
        /* affiche le menu */
        void show();
        /* cache le menu */
        void hide();
    private:
     
    friend class Application; // pour lui permettre d'accéder aux connexions sans se poser de questions ;)
                             // j'y viendrai plus tard ...
        std::map<std::string, Button> allButtons;
        Tools::Connection hideToRunConnection;
        Tools::Connection hideForMenuConnection;
    };
    #endif //MENU_H
    /* et dans le fichier d'implémentation (menu.cpp) */
    #include <menu.h>
    /* parce qu'un peu de sécurité ne fait jamais de tord :D */
    #include <cassert>
    void Menu::add(std::string const & text){
        assert(allButtons.find(text)==allButtons.end() && "button already exists");
        allButtons.insert(std::make_pair(text, Button{}));
    }
    Tools::Connection Menu::connectTo(std::string const & buttonKey, ClickedButtonSlot slot){
      auto  it = allButtons.find(buttonKey);
        assert (it != allButtons.end() && "inexistant button");
      return it->second.onClicked(slot); 
    }
    void Menu::show(){
        /* ce qu'il faut faire pour montrer le menu */
    }
    void Menu::hide(){
        /* ce qu'il faut faire pour cacher le menu */
    }
    Plus simple que cela, tu avoueras qu'il y a pas moyen hein?

    Et maintenant, il ne reste plus qu'à mettre "tout cela en musique"... Pour cela, nous allons avoir besoin de la notion ... d'application. La notion d'application sera -- pour faire simple -- le contexte général dans lequel tout ton jeu évoluera.
    Pour l'instant (parce que je me suis limité au stricte minimum ) elle va essentiellement permettre de mettre tous les éléments dont j'ai parlé "en musique". Elle devra essentiellement exposer un seul service : une fonction execute, qui sera exécutée par la fonction principale.

    Mais, en interne, elle aura quand meme pas mal de boulot, car c'est elle qui va s'amuser à connecter les différents slots au différents signaux qui existent. Voici à quoi elle pourrait ressembler:
    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
    45
    46
    47
    48
    /* dans le fichier d'en-tête (application.h)*/
    #ifndef APPLICATION_H
    #define APPLICATION_H
    /* on a besoin de Game, de Menu et de Inventory */
    #include <game.h>
    #include <menu.h>
    #include <inventory.h>
    class Application{
    public:
        explicit Application();
        int execute();
    private:
        /* parce que j'aime bien séparer les différentes responsabilités... Ca permet d'avoir un code plus facile à lire :D */
        void createMenu();
        void connectSignals();
        bool finished{false}; 
     
        Game game;
        Menu menu;
        Inventory inventory;
    };
    #endif // APPLICATION_H
    /* et dans le fichier d'implémentation (application.cpp) */
    #include <application.h>
    Application::Application(){
        createMenu();
        connectSignals();
    }
    int Application::execute(){
        while(! finished){
            menu.show();
        }
        return 0;
    }
    void Application::createMenu(){
        menu.add("play");
        menu.add("pause");
        menu.add("inventory");
    }
    void Application::connectSignals(){
        /* on commence par le menu, parce que le premier arrivé est le premier servi :D */
        menu.hideToRunConnection = menu.connectTo("play", [&](){menu.hide();});
        menu.hideForMenuConnection = menu.connectTo("inventory",[&](){menu.hide();});
        /* on peut utiliser différentes méthodes :D */
        game.runConnection = menu.connectTo("play",std::bind(&Game::run, &game));
        game.pauseConnection = menu.connectTo("play",std::bind(&Game::pause, &game));
        inventory.showConnection = menu.connectTo("inventory",[&](){inventory.show();});
    }
    Bon, je serai honnête, c'est un peu plus complexe , mais ce n'est quand même pas la mort, hein? (et puis, il y avait beaucoup à faire )

    Mais grâce à tout cela la fonction main pourra être marquée dans les annales comme l'une des plus compliquées que tu auras du écrire ( ) car elle prendra la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /* dans main.cpp */
    #include <application.h>
    int main(){
        Application app;
        return app.execute();
    }
    Pas mal, hein

    <warning> Je n'ai absolument pas essayé de compiler ce code, que j'ai écrit "à la volée" en même temps que je rédigeais mon intervention... Il se peut donc tout à fait que j'ai laissé passé quelques fautes d'attention, et je m'en excuse par avance </warning>

    NOTA: J'aurais certainement pu choisir très facilement parmi cinq autre manières supplémentaires pour organiser les choses différemment (entre autres, pour assurer la connexion des slots). Elles ne sont ni pires ni meilleures (de prime abord) que celle-ci. Elles sont juste différentes.

    Si j'ai choisi celle-ci, c'est parce que c'est la première qui me soit venue à l'esprit pendant que je "codais à vue"

    Maintenant, si je devais me poser et y réfléchir un tant soit peu, j'en choisirais peut être une autre, mais, comme c'est une intervention qui a demandé plus de 2h30 de rédaction, je crois avoir mérité un peu de repos

    EDIT : toutes les erreurs de typo ont été corrigées, et il ne manque donc que le moyen de compiler le projet. Je vous propose d'utiliser CMake avec ce CMakeLists.txt
    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
    cmake_minimum_required(VERSION 3.11)
    project(game )
    set(TGNAME ${PROJECT_NAME})
    set(INC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    set(HEADERS application
                button
                game
                inventory
                menu
                )
    set(SRCS application
             button
             game
             inventory
             main
             menu
    )
    foreach(H ${HEADERS})
        list(APPEND ALL_HEADERS ${INC_DIR}/${H}.h)
    endforeach()
    #parce qu'il n'a pas la même extension
    list(APPEND ALL_HEADERS ${INC_DIR}/Signal.hpp)
    foreach(H ${SRCS})
        list(APPEND ALL_SRCS ${SRC_DIR}/${H}.cpp)
    endforeach()
    add_executable(${TGNAME}  ${ALL_SRCS} ${ALL_HEADERS} )
    target_include_directories(${TGNAME} PUBLIC ${INC_DIR})
    target_include_directories(${TGNAME} PUBLIC
        $<BUILD_INTERFACE: ${INC_DIR}>
        $<INSTALL_INTERFACE:funECS/${TGNAME}>
    )
    Le code compile sans problème, mais, bien sur, à cause des points laissés en suspend, l'exécution est ... décevante : le programme tourne en boucle sans rien afficher
    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. #18
    Membre actif Avatar de JonathanTC
    Homme Profil pro
    Développeur Java
    Inscrit en
    Juillet 2015
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Juillet 2015
    Messages : 90
    Par défaut
    Ok merci pour toute ces explications , je vais voir sa de plus près en réécrivant tout ça dans des fichier et faire test.

  19. #19
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 492
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 492
    Billets dans le blog
    1
    Par défaut
    Hey ! J'avais envoyé par MP ma question car je n'avais pas participé à la discussion et le contenu de Signal.hpp n'avait pas été questionnée ici. Je ne voulais donc pas "détourner" le sujet initial.

    En relisant le code, j'ai compris pourquoi il faut garder l'objet Connection renvoyé : sinon, l'objet créé dans méthode connect() est détruit, et avec lui le unique_ptr qui fait la déconnexion. Subtil et en même temps intéressant

  20. #20
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Hey ! J'avais envoyé par MP ma question car je n'avais pas participé à la discussion et le contenu de Signal.hpp n'avait pas été questionnée ici. Je ne voulais donc pas "détourner" le sujet initial.
    Et ca partait d'une très bonne intention, mais, comme la réponse en elle même pouvait intéresser tout le monde (d'ici à ce que je mette tout sur github), j'ai préféré l'inclure.

    J'espère que tu ne m'en veux pas d'avoir "brisé" la confidentialité des MP

    Citation Envoyé par Bktero Voir le message
    En relisant le code, j'ai compris pourquoi il faut garder l'objet Connection renvoyé : sinon, l'objet créé dans méthode connect() est détruit, et avec lui le unique_ptr qui fait la déconnexion. Subtil et en même temps intéressant
    Merci pour l'appréciation

    J'aurais sans doute pu trouver une alternative qui m'aurait évité le recours à la connexion, mais j'aime garder les choses simples
    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

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

Discussions similaires

  1. passer le nom d'une fonction comme paramètre d'une autre fonction?
    Par med_alpa dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 11/03/2010, 12h57
  2. Réponses: 16
    Dernier message: 26/05/2009, 13h32
  3. Réponses: 15
    Dernier message: 02/08/2007, 11h17
  4. fonction en paramètre d'une autre fonction
    Par zorobab dans le forum Général Python
    Réponses: 3
    Dernier message: 25/03/2007, 13h34
  5. [VB]Passage de Fonction en paramètre (d'une autre fonction)
    Par Australia dans le forum VB 6 et antérieur
    Réponses: 10
    Dernier message: 21/03/2006, 18h55

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