boost.Signals, boost.Bind et gestion d’évènements en C++

Pouet ! Une fois n’est pas coutume, un peu de programmation (ouais, ça change du Parkour et de la grimpe :) Bon, ici, je vais faire une petite démonstration de l’utilisation des librairies boost pour gérer les évènements en C++.

Prenons un exemple tout con : on a un programme dans lequel nous avons un gestionnaire de resource, et une zoulie interface graphique. Le gestionnaire est capable de créer, modifier, supprimer des resources. Bien. Maintenant imaginons que nous ayons une fenêtre de notre interface qui affiche en continue toutes les resources actuellement gérées par notre programme, et qu’en même temps, nous ayons une fenêtre dans laquelle s’affiche en temps réel les actions effectuée sur les resources (ce qu’on appelle un log :)

La plupart des mauvais développeurs ne se prendraient pas la tête et mixeraient allègrement les différentes classes du programme, pour mettre en dur la mise à jour de l’interface dans le gestionnaire, ou vice versa … ce qui est TRES TRES PORCASSE ! Au lieu de tout mélanger, y’a un système très intéressant qu’on pourrait désigner par “gestion d’évènements” ou callbacks, ou delegates … j’ai jamais trop bien compris les légères / subtiles différences entre toutes les appellations de la même chose. En gros, dans ce monde, le gestionnaire de ressource se contente juste de gueuler “j’ai créé une ressource !!” et les autres parties, genre le log et l’interface se contentent d’écouter, et quand ça les intéresse (genre là, y’a “ressource dans le message, ça m’intéresse !) pouf on récupère l’évènement.

C’est très bisounours land, donc ci-dessous un exemple :

#include
#include
#include

using namespace std;

/*
    Un gestionnaire de resources.
*/
class ResourceManager
{
public:

    /*
        Ce signal permet à d'autres parties du programme de "s'enregistrer"
        afin d'être notifiées lorsque le ResourceManager effectue une action.
        Ici, il stock des fonctions ayant la signature "void (int, const char *)"
        C'est à dire des fonctions retournant void, et acceptant 2 paramètres : un
        int et une chaine de caractère. On peut spécifier le nombre d'argument que
        l'on veut, le type que l'on veut.
    */
    boost::signal < void (int, const char * ) > Signal;

    /*
        Ci dessous 2 méthodes d'exemple. Chaque méthode lance le signal avec les
        paramètres que l'on veut. Ici, l'id de la resource et un message indiquant
        l'action ayant été effectuée.
    */
    void CreateResource(int id) { Signal(id, "Resource created"); }
    void DeleteResource(int id) { Signal(id, "deleted resource"); }
};

/*
    Cette classe est un exemple de log. Elle écrit simplement tout ce qui
    se passe dans la sortie texte. Elle possède une méthode ayant la même
    signature que celle du signal ResourceManager::ResourceSignal.
*/
class Log
{
public:
    void ResourceEvent(int id, const char * msg)
    {
        cout << msg << "  : " << id << endl;
    }
};

/*
    Un deuxième exemple de classe qui aurait besoin d'être notifiée des actions
    sur les resources : une interface utilisateur qui affiche l'état actuel
    des resources. Comme la classe de log, cette classe possède une méthode
    ayant la bonne signature pour recevoir les signaux de
    ResourceManager::ResourceSignal.
*/
class ResourceUI
{
public:
    void Event(int id, const char * msg)
    {
        cout << "updating UI ..." << endl;
    }
};

int main(int argc, char* argv[])
{
    // 1 instance de chacune des classes
    ResourceManager resourceMgr;
    Log log;
    ResourceUI ui;

    /*
        on connecte le tout via boost::bind. Le premier argument est le pointeur de
        la méthode à appeler. Le deuxième est le pointeur sur l'instance dont on veut
        appeler la méthode. Les 2 derniers paramètres signifient juste que lors de
        l'appelle du signal, les paramètres 1 et 2 du signal seront reroutés sur
        la méthode. Voir plus bas dans l'article pour la doc de boost::bind
    */
    resourceMgr.Signal.connect(boost::bind(&Log::ResourceEvent, &log, _1, _2));
    resourceMgr.Signal.connect(boost::bind(&ResourceUI::Event, &ui, _1, _2));

    // et pour tester, on créer une resource, et on en détruit une autre :
    resourceMgr.CreateResource(1);
    resourceMgr.DeleteResource(2);

    /*
        le résultat qui s'affiche est :

        Resource created : 1
        updating UI ...
        deleted resource : 2
        updating UI ...
    */

    // on peut aisément déconnecter un slot (ici, on arrête le log)
    resourceMgr.Signal.disconnect(boost::bind(&Log::ResourceEvent, &log, _1, _2));

    // on reteste :
    resourceMgr.CreateResource(1);
    resourceMgr.DeleteResource(2);

    /*
        le résultat qui s'affiche est :

        updating UI ...
        updating UI ...
    */

    // pause
    string str;
    getline(cin, str);

    return 0;
}

Wouhou ça fait un gros paté de code. Mais si vous avez pris le temps de lire les commentaires, et regarder un peu ces 3 classes, le truc de ouf, c'est que aucune classe ne fait appelle à aucune autre ! Chacune gère simplement son bazard sans rien savoir de ce qui se passe autour d'elle ! Et c'est ça qui est bien. Je peux rajouter un nouveau système de log et le connecter à mon resource manager sans avoir à toucher la moindre ligne de code de la classe ResourceManager !

Voilou, tout cet article, c'était pour partager ma découverte de boost::signal et boost::bind, qui permettent de créer un système de callbacks très simple d'utilisation est très puissant.

2 thoughts on “boost.Signals, boost.Bind et gestion d’évènements en C++

  1. Exactly, d’après ce que j’en ai lu, c’est tellement proche des signaux / slots de Qt que tu peux assez facilement créer un wrapper pour connecter des slots boost sur des signaux Qt (et vice versa)

    Bon, là j’suis quand même en train de galérer pour récupérer une fonctionnalité que j’avais dans le système que j’avais codé à la main : pouvoir récupérer la liste des slots, et les appeler un par un, en checkant la valeur de retour (si y’en a une) pour chaque appel. A priori c’est possible, mais faut créer une vieille classe template toute moisie que j’ai pas encore compris comment faire marcher … pas simple.

    Mais sinon pour le reste, l’utilisation “standard” c’est trop bieng ! :)

  2. Je crois bien que je suis dans la première catégorie! Et ça doit être pour ça que mon prog il sature et fait tout planter… mwahaa!

    ca ressemble un peu qux signals and slot de Qt ton truc, non? Ce qui doit être très pratique quand on utilise pas Qt… :)

Comments are closed.