2012-12-01 6 views
1

Pointeurs de fonction de magasin avec différents paramètres dans un vecteur de pointeurs de vide.Convertir void * en fonction std :: <void()>

unordered_map<string, vector<void*> > List; 

template <typename T> 
void Listen(string Name, function<void(T)> Function) 
{ 
    List[Name].push_back(&Function); 
} 

Ensuite, je veux les appeler, en supposant que T est le même type que celui utilisé pour Fire pour le Listen.

template <typename T> 
void Fire(string Name, T Data) 
{ 
    auto Functions = List[Name]; 

    for (auto i = Functions.begin(); i != Functions.end(); ++i) 
    { 
     (function<void(T)>)i)(Data); 
    } 
} 

Mais j'obtiens une erreur de compilation qui lit error C2064: term does not evaluate to a function taking 1 arguments in file ...\vc\include\xrefwrap 431 1.

Qu'est-ce que je fais mal?

+0

Vous appuyez sur l'adresse d'un paramètre. – chill

+1

Pourquoi ne pas enregistrer 'std :: function' dans la' List'? – Lol4t0

+0

@ Lol4t0. Malheureusement, je ne peux pas enregistrer différents types dans la même liste. Par exemple 'function ', 'fonction ' et 'function ' sont tous différents types. – danijar

Répondre

2

D'une part, vous prenez l'adresse d'un paramètre, ici:

List[Name].push_back(&Function); 

Ensuite, vous essayez de convertir un objet iterator à un objet std::function ici:

(function<void(T)>)i) 

Qu'est-ce que vous essayez de faire peut être fait, comme ceci, bien que ce ne soit pas joli, pour le dire légèrement:

unordered_map<string, vector<void*> > List; 

template <typename T> 
void Listen(string Name, function<void(T)> &Function) 
{ 
    List[Name].push_back(&Function); 
} 

template <typename T> 
void Fire(string Name, T Data) 
{ 
    auto Functions = List[Name]; 

    for (auto i = Functions.begin(); i != Functions.end(); ++i) 
    { 
     function<void(T)> *ptr = *i; 

     (*ptr) (Data); 
    } 
} 

Il peut briser dans beaucoup de façons, par exemple, vous avez aucun contrôle que la fonction, enregistrée sous un nom dans Listen est appelé avec l'argument correct dans Fire - envisager d'appeler Listen<int> ("foo", f); et faire ensuite Fire<double> ("foo", 3.14);

Une autre approche - il suffit de passer fermetures pour callbacks:

unordered_map<string, std::vector<function<void()> > > List; 

void Listen(string Name, function<void()> Function) 
{ 
    List[Name].push_back(Function); 
} 

void Fire(string Name) 
{ 
    auto Functions = List[Name]; 

    for (auto i = Functions.begin(); i != Functions.end(); ++i) 
     (*i)(); 
} 
+0

Pourquoi '&' est-il utilisé sur le paramètre de la fonction 'Listen'? Puis-je éviter cela? – danijar

+0

Ceci est nécessaire pour s'assurer que la fonction est appelée avec une lvalue, c'est-à-dire avec quelque chose qui a une adresse. Vous devez toujours vous assurer que l'adresse reste valide tant qu'elle se trouve dans le vecteur/la carte. – chill

1
#include <functional> 
#include <unordered_map> 
#include <memory> 
#include <string> 
#include <vector> 

template<typename T> struct BlockDeduction{typedef T type;}; 
struct BaseCallback { 
    virtual ~BaseCallback(); 
    template<typename T> 
    void DoCall(typename BlockDeduction<T>::type&& t) const; 
}; 
template<typename T> 
struct Callback: BaseCallback 
{ 
    std::function<void(T)> func; 
    Callback(std::function<void(T)> const& f):func(f) {} 
}; 


template<typename T> 
void BaseCallback::DoCall(typename BlockDeduction<T>::type&& t) const { 
    Assert(dynamic_cast<Callback<T>const*>(this)); 
    static_cast<Callback<T>const*>(this).func(std::forward(t)); 
} 

typedef std::unique_ptr<BaseCallback> upCallback; 
template<typename T> 
upCallback make_callback(std::function<void(T)> const& f) { 
    return upCallback(new Callback<T>(f)); 
} 


struct Listener { 
    std::unordered_map< std::string, std::vector<upCallback>> List; 
    template<typename T> 
    void Listen(std::string Name, std::function<void(T)> f) { 
    List[Name].push_back(make_callback(f)); 
    } 
    template<typename T> 
    void Fire(std::string Name, typename BlockDeduction<T>::type&& t) { 
    auto callbacks = List.find(Name); 
    if (callbacks == List.end()) return; 
    for(auto it = callbacks->second.begin(); it != callbacks->second.end(); ++it) { 
     if (it +1 = callbacks->second.end()) 
     { 
     (**it).DoCall<T>(std::forward(t)); 
     } else { 
     (**it).DoCall<T>(t); 
     } 
    } 
    } 
}; 

... ou quelque chose comme ça.

Ceci stocke une copie du std::function dans la carte, encapsulée de manière générique. La mémoire est gérée via un unique_ptr. J'ai soigneusement bloqué la déduction de type à des points où le type doit être exactement ce que vous avez utilisé lorsque vous avez installé le Listener (la déduction de type automatique à ce moment-là est plutôt fragile).

En débogage, vous obtiendrez un échec d'assertion si vous ne respectez pas le mappage de type Nom < ->.

Un travail supplémentaire doit être effectué pour les rappels inutiles. Il suffit d'écrire un DoCall qui jette BaseCallback-Callback<void>, se spécialisent Callback<void> être un emballage function arité, se spécialisent make_callback sur arité function et écrire une méthode Fire(string) pour Listener qui appelle la DoCall nue.

Ou créer un struct Empty et utiliser lambdas pour envelopper les fonctions nulles dans function<void(Empty)>, ce qui impliquerait un peu moins de code, mais serait plus lent au moment de l'exécution.

+0

J'aime ça mais j'ai besoin de temps pour vraiment comprendre tout ce que vous avez fait. – danijar

+0

Notez que la merde 'std :: forward' est peut-être fausse - j'essaie toujours de l'ajouter. Je ne devrais probablement pas l'avoir inclus, et juste fait quelques copies redondantes de type 'T'. 'BlockDeduction' n'existe que pour bloquer la déduction automatique du type de fonction (parce que le' T' que prend 'Fire' doit correspondre EXACTEMENT au même type que le' T' 'Listen', ou des résultats de comportement indéfinis). Le 'BaseCallback' existe vraiment pour avoir juste un dteur virtuel (ainsi sa classe enfant dtor est appelée), et la méthode' DoCall' met juste le 'static_cast' dangereux dans la classe. 'unique_ptr' gère la durée de vie. – Yakk

Questions connexes