Faisant suite au précédent billet, celui-ci va pointer une grande difficulté du C++, qui est la gestion des pointeurs de fonctions sur les objets.
Pointeurs de fonction
La syntaxe C est très simple, pour faire un pointeur vers une fonction, il suffit d'une ligne :
void f(int arg) {} void (*callback) (int arg) = &f; callback(5);
En C++, c'est un peu plus complexe. Le pointeur de fonction représentant une adresse mémoire, on ne peut pas dans un seul pointeur associer la méthode pointée, et l'objet appelé avec cette méthode. Ceci est impossible en C++ :
class A { public: bool f() { return true; } }; A a; bool (*callback)() = &a.f; //error
Il est cependant possible de garder le pointeur vers la méthode (statiquement), puis d'appeler cette méthode sur un objet donné. Ce qui donne :
bool (A::*callback)() = &A::f; (a.*func)();
Callbacks
Il est possible, en C++, de masquer ce mécanisme, grâce à une classe dédiée du genre :
class _CallBack { public: virtual ~_CallBack() {} virtual bool run() = 0; }; template<typename T> class CallBack : public _CallBack { public: typedef bool (T::*TFunc) (); CallBack(T* _obj, TFunc _func) : obj(_obj), func(_func) {} virtual bool run() { return (obj->*func) (); } private: T* obj; TFunc func; };
Pour garder un callback, il devient alors juste nécessaire d'avoir un pointeur vers une instance de CallBack :
_CallBack* cb = new CallBack<A>(a, &A::f); cb->run(); // calls a.f()
Il est à noter que comme CallBack est une classe template, il est nécessaire de garder un pointeur vers sa classe de base _CallBack, et par polymorphisme les appels seront correctement effectués.
Compatibilité C
Il arrive parfois qu'il est nécessaire d'utiliser une bibliothèque C qui, évidement, requiert un pointeur vers une fonction. Il est relativement fréquent que les méthodes de définition de callback prenne un argument void* data, permettant ainsi de récupérer le contexte dans le callback.
Il est dans ce cas là possible de trouver une parade, grâce à la méthode suivante :
bool g_callback(void* data) { _CallBack* cb = static_cast<_CallBack*>(data); if(!data) { b_log[W_ERR] << "g_callback() error"; return false; } return cb->run(); }
read_cb = new CallBack(a, &A::f); glib_input_add(fd, PURPLE_INPUT_READ, g_callback_input, read_cb);
Dans cet exemple, chaque fois qu'un événement en lecture se produit sur le descripteur de fichier fd, l'appel est effectué à a.f(). Évidement, il faut faire très attention à ce que a ne soit pas libéré.
Le cas sans solution
Malheureusement, dans le cas où la bibliothèque ne permet pas de définir un attribut data arbitraire, il est impossible de retrouver le contexte, mis à part en misant aléatoirement sur la valeur des éventuels arguments du callback. Si il n'y en a pas, il n'y a pas de solution.
C'est le cas avec la libpurple, dans le cadre de mon projet bitlbee2, qui prends des instances de structures contenant des pointeurs de fonctions sans pouvoir spécifier aucun argument arbitraire, et bien évidement sans aucun argument de contexte.
0 Responses to “[C++] Des défauts du C++ (Part 2) : pointeurs de fonctions”