Re: Callbacks en C++

Page principale

Répondre à ce message
Auteur: Edgar Bonet
Date:  
À: guilde
Sujet: Re: Callbacks en C++
Bonjour !

Frédéric a écrit :
> Je cherche à implémenter des callbacks vers des méthodes en C++ sur
> Arduino.


Et voici un sujet qui a fait couler beaucoup d'électrons sur
arduino.stackexchange ! Je vais essayer de résumer les possibilités.

Commençons par un exemple simple. Voici une classe qui stocke un
callback pour s'en servir à l'occasion :

    class Caller {
    public:
        Caller(void (*cb)()) : callback(cb) {}
        void call() { callback(); }
    private:
        void (*callback)();
    };


Cette classe attend, en guise de callback, une fonction nue. Tu peux
t'en servir comme ça :

    void say_hello() { Serial.println("Hello, World!"); }


    void setup()
    {
        Serial.begin(9600);
        Caller b(say_hello);
        b.call();
    }


Maintenant, mettons que tu veux encapsuler le callback dans une classe.
Le plus simple est d'en faire une méthode statique de cette classe :

    class Callee {
    public:
        static void say_hello() { Serial.println("Hello, World!"); }
    };


    void setup()
    {
        Serial.begin(9600);
        Callee a;
        Caller b(a.say_hello);
        b.call();
    }


Ça marche parce qu'une méthode statique est équivalente à une fonction
nue. Malheureusement, tu ne peux pas toujours utiliser une méthode
statique. Si ton callback a besoin d'accéder à des données de la classe,
ça marche en rendant ces données statiques. Mais si le callback a besoin
d'accéder à des données de *une instance* particulière de la classe, ce
callback ne peut pas être une méthode statique. Pour utiliser un tel
callback il faut :

  - que l'appelant stocke, en plus du pointeur vers le callback, une
    référence à (ou un pointeur vers) l'instance que tu veux utiliser ;


  - que le callback soit déclaré comme un pointeur vers une instance
    d'une classe spécifique, et non un simple pointeur de fonction.


La syntaxe devient un peu tordue quand tu manipules des pointeurs
d'instance. Voici un exemple :

    class Callee {
    public:
        void say_hello() { Serial.println("Hello, World!"); }
    };


    class Caller {
    public:
        Caller(Callee &ob, void (Callee::*cb)())
        : object(ob), callback(cb) {}
        void call() { (object.*callback)(); }
    private:
        Callee &object;
        void (Callee::*callback)();
    };


    void setup()
    {
        Serial.begin(9600);
        Callee a;
        Caller b(a, &Callee::say_hello);
        b.call();
    }


En C++ moderne, il y a moyen d'encapsuler le pointeur vers l'instance et
le pointeur vers la méthode dans un std::function, dont l'usage est
vraiment commode avec des lambdas. Seulement, dans l'environnement
Arduino, les facilités du C++ moderne ne sont pas toujours disponibles.
En particulier, sur AVR, tu as C++11 mais pas la STL, et donc pas
std::function. On est donc réduits à des méthodes plus pédestres, comme
dans le code ci-dessus.

À+,

Edgar.