2013-03-01 4 views
3

Ok si souvent fois je l'ai vu le type suivant de gestion des événements utilisés:C++ invocation de fonction membre

Connect(objectToUse, MyClass::MyMemberFunction);

pour une sorte de gestion des événements où objectToUse est du type MyClass. Ma question est de savoir comment cela fonctionne exactement. Comment le convertir en quelque chose qui ferait objectToUse->MyMemberFunction()

Est-ce que le MyClass::MyMemberFunction donne un décalage depuis le début de la classe qui peut alors être utilisé comme un pointeur de fonction?

Répondre

2

En plus de la réponse de Mats, je vais vous donner un court exemple de la façon dont vous pouvez utiliser une fonction membre non statique dans ce type de chose. Si vous n'êtes pas familier avec les pointeurs vers les fonctions membres, vous pouvez d'abord consulter le FAQ.

Ensuite, considérez ceci (plutôt simpliste) exemple:

class MyClass 
{ 
public: 
    int Mult(int x) 
    { 
     return (x * x); 
    } 

    int Add(int x) 
    { 
     return (x + x); 
    } 
}; 

int Invoke(MyClass *obj, int (MyClass::*f)(int), int x) 
{ // invokes a member function of MyClass that accepts an int and returns an int 
    // on the object 'obj' and returns. 
    return obj->*f(x); 
} 

int main(int, char **) 
{ 
    MyClass x; 

    int nine = Invoke(&x, MyClass::Mult, 3); 
    int six = Invoke(&x, MyClass::Add, 3); 

    std::cout << "nine = " << nine << std::endl; 
    std::cout << "six = " << six << std::endl; 

    return 0; 
} 
+0

Donc - '-> *' est-il réellement les opérateurs -> et * comme je suppose qu'ils sont ou est-ce un opérateur spécial séparé? – csteifel

+2

Non '-> *' est un opérateur * distinct * de '->' et '*'. Consultez la FAQ - c'est à http://www.parashift.com/c++-faq/pointers-to-members.html pour plus d'informations. Il explique assez bien les indications aux membres. –

2

Typiquement, celui-ci utilise une fonction de membre static (qui prend un pointeur d'argument), auquel cas le l'objectToUse est passée en tant que paramètre, et le MyMemberFunction utiliserait objectToUse de mettre en place un pointeur vers un objet MyClass et utilisez-le pour faire référence aux variables membres et aux fonctions membres.

Dans ce cas Connect contiendra quelque chose comme ceci:

void Connect(void *objectToUse, void (*f)(void *obj)) 
{ 
    ... 
    f(objectToUse); 
    ... 
} 

[Il est également tout à fait possible que f et objectToUse sont enregistrés quelque part à utiliser plus tard, plutôt que réellement à l'intérieur connnect, mais l'appel serait regarde la même chose dans ce cas aussi - juste à partir d'une autre fonction appelée comme une conséquence de l'événement auquel cette fonction est supposée être appelée). Il est également POSSIBLE d'utiliser un pointeur vers la fonction membre, mais c'est assez complexe, et pas du tout facile à "corriger" - à la fois en termes de syntaxe et "quand et comment vous pouvez l'utiliser correctement". Voir plus .

Dans ce cas, Connect regarderait un peu comme ceci:

void Connect(MyClass *objectToUse, void (Myclass::*f)()) 
{ 
    ... 
    objectToUse->*f(); 
    ... 
} 

Il est très probable que les modèles sont utilisés, comme si l'on sait « MyClass » dans la classe Connect, il serait assez inutile de avoir un pointeur de fonction. Une fonction virtuelle serait un bien meilleur choix. Dans les bonnes circonstances, vous pouvez également utiliser des fonctions virtuelles en tant que pointeurs de fonction membre, mais cela nécessite que le compilateur/l'environnement «joue». Voici quelques détails sur ce sujet [dont je n'ai aucune expérience personnelle: Pointeurs vers des fonctions membres virtuelles. Comment ça marche? Functors, qui est un objet enveloppant une fonction, permet de transmettre un objet avec un comportement spécifique en tant que «objet fonction». Cela implique généralement une fonction membre prédéfinie ou un operatorXX appelé dans le cadre du traitement dans la fonction qui doit rappeler dans le code.

C++ 11 autorise les "fonctions Lambda", qui sont des fonctions déclarées à la volée dans le code, qui n'ont pas de nom. C'est quelque chose que je n'ai pas utilisé du tout, donc je ne peux pas vraiment commenter plus loin - j'ai lu à ce sujet, mais je n'avais pas besoin de l'utiliser dans ma programmation (passe-temps) - la plus grande partie de ma vie professionnelle est avec C, plutôt que C++ bien que j'ai travaillé pendant 5 ans avec C++ aussi.

+0

En particulier, voir [ici] (http://www.parashift.com/c++-faq/macro-for-ptr-to-memfn.html). –

+0

Il est utilisé comme un pointeur vers une fonction membre __plus souvent que comme une méthode statique. –

+0

De quel environnement parlez-vous, Vlad?De nombreux systèmes basés sur des événements sont basés sur C et ne répondent pas du tout aux fonctions membres de style C++, ce qui nécessite l'utilisation de fonctions statiques. Dans les environnements plus orientés C++, j'aurais pensé qu'une classe d'interface serait plus une solution appropriée, où "MyMemberFunction" est l'une des interfaces, et est appelée directement dans la fonction "Connect" [ou plus tard en tant que résultat de l'interface transmise à la fonction Connect]. –

0

Pour la gestion des événements ou des rappels, ils prennent généralement deux paramètres: une fonction de rappel et un argument userdata. La signature de la fonction de rappel aurait des données d'utilisateur comme l'un des paramètres.

Le code qui invoque l'événement ou le rappel appelle la fonction directement avec l'argument userdata. Quelque chose comme ceci par exemple:

eventCallbackFunction(userData); 

Dans votre gestion des événements ou la fonction de rappel, vous pouvez choisir d'utiliser le userdata à faire quoi que ce soit que vous voulez.

Étant donné que la fonction doit pouvoir être appelée directement sans objet, il peut s'agir d'une fonction globale ou d'une méthode statique d'une classe (qui ne nécessite pas de pointeur d'objet).

Une méthode statique a des limitations qu'elle peut uniquement accéder aux variables de membre statiques et appeler d'autres méthodes statiques (puisqu'elle n'a pas le ce pointeur). C'est là que userData peut être utilisé pour obtenir le pointeur d'objet.

Avec tout cela l'esprit, jetez un oeil à l'extrait de code exemple suivant:

class MyClass 
{ 
... 
public: 
    static MyStaticMethod(void* userData) 
    { 
     // You can access only static members here 
     MyClass* myObj = (MyClass*)userdata; 
     myObj->MyMemberMethod(); 
    } 

    void MyMemberMethod() 
    { 
     // Access any non-static members here as well 
     ... 
    } 

... 
... 
}; 

MyClass myObject; 
Connect(myObject, MyClass::MyStaticMethod); 

Comme vous pouvez le voir, vous pouvez même accéder à des variables et des méthodes membres dans le cadre de la gestion des événements si vous pouviez faire une statique méthode qui serait invoquée en premier et qui enchaînerait l'appel à une méthode membre en utilisant le pointeur d'objet (récupéré depuis userData).

1

Je peux me tromper, mais pour autant que je comprends,

En C++, les fonctions avec la même signature sont égaux.

Les fonctions membres C++ avec n paramètres sont en fait des fonctions normales avec n + 1 paramètres. En d'autres termes, void MyClass::Method(int i) est en vigueur void (some type)function(MyClass *ptr, int i). Par conséquent, je pense que la façon dont Connect fonctionnerait en arrière-plan consiste à transformer la signature de la méthode membre en une signature de fonction normale. En d'autres termes, il faudrait essentiellement utiliser des pointeurs vers des fonctions et les convertir en un type plus générique jusqu'à ce qu'il soit possible de le faire. être appelé avec les paramètres fournis et le paramètre supplémentaire, qui est le pointeur sur l'instance de l'objet

Si la méthode est statique, alors un pointeur sur une instance n'a pas de sens et c'est une conversion de type droite. Je n'ai pas encore compris les complexités impliquées par les méthodes non-statiques - un regard sur les internes de boost::bind est probablement ce que vous voulez faire pour comprendre cela :) Voici comment cela fonctionnerait pour une fonction statique.

#include <iostream> 
#include <string> 

void sayhi(std::string const& str) 
{ 
    std::cout<<"function says hi "<<str<<"\n"; 
} 

struct A 
{ 
    static void sayhi(std::string const& str) 
    { 
     std::cout<<"A says hi "<<str<<"\n"; 
    } 
}; 

int main() 
{ 
    typedef void (*funptr)(std::string const&); 
    funptr hello = sayhi; 
    hello("you"); //function says... 
    hello = (&A::sayhi); //This is how Connect would work with a static method 
    hello("you"); //A says... 
    return 0; 
} 
+0

Vous en êtes au point où les fonctions virtuelles, l'héritage multiple et/ou l'héritage virtuel entrent dans l'image (c'est pourquoi le pointeur sur la fonction est habituellement juste sizeof (void *) 'bytes et le pointeur sur le membre est généralement deux fois plus grand. +1 cependant. –

Questions connexes