2010-04-27 5 views
1

J'ai une bibliothèque C++ qui devrait exposer certains appels system \ resource comme des rappels de l'application liée. Par exemple: l'application d'interfaçage (qui utilise cette bibliothèque) peut envoyer des fonctions de rappel de gestion de socket - envoyer, recevoir, ouvrir, fermer, etc., et la bibliothèque utilisera cette implémentation au lieu de l'implémentation de la bibliothèque. (Ceci permet à l'application de gérer les sockets elle-même, peut être utile). Cette bibliothèque doit exposer également plus de rappels, comme, par exemple, une validation de mot de passe, donc je me demande s'il existe une méthode préférée pour exposer l'option d'envoi de rappel dans une API. Quelque chose comme:Quelle est la meilleure façon d'exposer une API de rappel - C++

int AddCallbackFunc (int functionCallbackType, <generic function prototype>, <generic way to pass some additional arguments>) 

Ensuite, au sein de ma bibliothèque je vais assigner le rappel au pointeur de fonction appropriée en fonction du paramètre de functionCallbackType.

Existe-t-il un moyen de l'implémenter de manière générique qui conviendra à TOUT prototype de fonction et à TOUT autre argument? Votre aide sera plus qu'appréciée ... Merci!

+0

Qu'en est-il d'une interface? Mutateurs virtuels pour définir les "arguments" et les fonctions virtuelles pour les rappels. –

Répondre

3

Pourquoi ne pas l'accepter 0 argument functor et juste l'utilisateur doit utiliser boost::bind pour construire les arguments dedans avant de l'enregistrer? Fondamentalement exemple (appels au lieu de magasins, mais vous obtenez le point):

#include <tr1/functional> 
#include <iostream> 

void callback(const std::tr1::function<int()> &f) { 
    f(); 
} 

int x() { 
    std::cout << "x" << std::endl; 
    return 0; 
} 

int y(int n) { 
    std::cout << "y = " << n << std::endl; 
    return 0; 
} 

int main(int argc, char *argv[]) { 
    callback(x); 
    callback(std::tr1::bind(y, 5)); 
} 

EDIT: Il y a une option B, qui consiste à mettre en œuvre essentiellement ce bind sous le capot avec des structures pour stocker tous les besoins info et héritage pour le polymorphisme ... ça devient un gâchis vraiment rapide. Je ne le recommanderais pas, mais cela fonctionnera. Vous pouvez également sauver du chagrin en forçant un type de retour de int, mais cela ne vous sauve qu'un peu.

#include <iostream> 

struct func_base { 
    virtual int operator()() = 0; 
}; 

// make one of these for each arity function you want to support (boost does this up to 50 for you :-P 
struct func0 : public func_base { 
    typedef int (*fptr_t)(); 

    func0(fptr_t f) : fptr(f) { 
    } 

    virtual int operator()() { return fptr(); } 

    fptr_t fptr; 
}; 

// demonstrates an arity of 1, templated so it can take any type of parameter 
template <class T1> 
struct func1 : public func_base { 
    typedef int (*fptr_t)(T1); 

    func1(fptr_t f, T1 a) : fptr(f), a1(a) { 
    } 

    virtual int operator()() { return fptr(a1); } 

    fptr_t fptr; 
    T1 a1; 
}; 

void callback(func_base *f) { 
    (*f)(); 
} 

int x() { 
    std::cout << "x" << std::endl; 
    return 0; 
} 

int y(int n) { 
    std::cout << "y = " << n << std::endl; 
    return 0; 
} 

int main(int argc, char *argv[]) { 
    // NOTE: memory leak here... 
    callback(new func0(x)); 
    callback(new func1<int>(y, 5)); 
} 
+0

Merci - mais je ne veux pas utiliser de bibliothèques externes. – rkellerm

+1

Malheureusement, c'est le meilleur moyen de faire cela, vous pouvez toujours utiliser tr1. À court d'implémenter fondamentalement ce que le bind fait manuellement (une entreprise massive) je ne vois pas un moyen facile de le faire gentiment. –

0

On dirait que vous recherchez un Functor. Fondamentalement une classe pour chaque type de rappel, avec les arguments en tant que membres de données et operator() pour appeler la fonctionnalité.

+0

Oui, le seul problème restant est de trouver un moyen de stocker différents foncteurs (tous ne prennent pas de paramètres dans 'operator()' mais sont techniquement différents types dans une collection afin qu'ils puissent être appelés plus tard génériquement. boost/tr1 'function <>' –

+0

sûr, habituellement avec des foncteurs vous auriez une classe de base commune, ce qui leur permettrait d'être stockés dans une collection – marijne

1

Si vous ne voulez pas utiliser l'une des options C++ disponibles; std :: tr1 :: function, functors, polymorphism avec la classe de base commune etc. vous pouvez utiliser la méthode C à la place.

Le client passe un rappel et un pointeur vers ses arguments sous la forme d'un void *, puis le callback envoie le void * au type correct lorsqu'il est appelé. Vous aurez besoin de stocker le vide * à côté du rappel et vous devrez faire très attention à la durée de vie des objets.

int AddCallbackFunc (int type, int(*callback)(void*), void* callbackData) 
+0

Et l'utilisation de 'void *' est probablement pourquoi il a été jugé dangereux dans la première place –

+0

Mais est-ce qu'il y a un autre moyen de faire ça? – rkellerm

1

Cela peut être fait en utilisant une combinaison de gabarit et d'effacement de type.

L'idée est de prendre n'importe quel type et l'envelopper dans un objet avec une interface connue.

class CallbackBase 
{ 
public: 
    virtual ~CallbackBase(); 

    virtual void execute(); 
}; 

template <class T> 
class Callback: public CallbackBase 
{ 
public: 
    explicit Callback(T functor): mFunctor(functor) {} 

    void execute() { mFunctor(); } 

private: 
    T mFunctor; 
}; 

Et maintenant, nous pouvons l'envelopper:

template <class Function> 
int AddCallbackFunc (int functionCallbackType, Function f) 
{ 
    std::auto_ptr<CallbackBase> c(new Callback<Function>(f)); 

    // do something with `c`. 
} 

Je laisse à vous de lier les arguments, la bibliothèque aucune façon est de créer un foncteur.

Questions connexes