91 votes

Comment puis-je passer une fonction membre de classe en tant que rappel?

Je suis en utilisant une API qui m'oblige à passer un pointeur de fonction comme un rappel. Je suis en train d'utiliser cette API à partir de ma classe mais j'obtiens des erreurs de compilation.

Voici ce que j'ai fait de mon constructeur:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Ce n'est pas de la compilation j'obtiens l'erreur suivante:

Erreur 8 erreur C3867: 'CLoggersInfra::RedundencyManagerCallBack': appel d'une fonction manquante de la liste d'arguments; utiliser '&CLoggersInfra::RedundencyManagerCallBack " pour créer un pointeur de membre

J'ai essayé la suggestion d'utiliser &CLoggersInfra::RedundencyManagerCallBack - n'a pas fonctionné pour moi.

Toutes les suggestions/explication pour cela??

Je suis l'aide de VS2008.

Merci!!

ofer

158voto

Joseph Garvin Points 6074

C'est une simple question mais la réponse est étonnamment complexe. La réponse courte est que vous pouvez faire ce que vous essayez de faire avec std::bind1st ou boost::bind. Plus la réponse est ci-dessous.

Le compilateur est correct pour vous suggérons d'utiliser &CLoggersInfra::RedundencyManagerCallBack. Tout d'abord, si RedundencyManagerCallBack est une fonction de membre de la fonction elle-même n'appartient pas à une quelconque instance de la classe CLoggersInfra. Il appartient à la classe elle-même. Si vous avez déjà appelé une classe statique fonction avant, vous avez peut-être remarqué que vous utilisez le même SomeClass::SomeMemberFunction de la syntaxe. Étant donné que la fonction elle-même est "statique" dans le sens où il appartient à la classe plutôt que d'un exemple particulier, vous utilisez la même syntaxe. Le " & " est nécessaire parce que techniquement parlant, on ne passe pas directement les fonctions de -- fonctions ne sont pas de vrais objets en C++. Au lieu de cela, vous êtes techniquement en passant l'adresse de la mémoire pour la fonction, c'est un pointeur à l'endroit où la fonction est de donner les consignes en mémoire. La conséquence est la même, vous êtes effectivement " passer une fonction en paramètre.

Mais c'est seulement la moitié du problème dans ce cas. Comme je l'ai dit, RedundencyManagerCallBack la fonction n'est pas "appartenir" à un cas particulier. Mais il semble que vous voulez passer comme un rappel avec une instance particulière à l'esprit. Pour comprendre comment ce faire, vous devez comprendre ce que les fonctions de membre sont vraiment: régulier non défini-dans-toute-la classe des fonctions avec un supplément de paramètre caché.

Par exemple:


class A {
public:
 Un (une) : les données(0) {}
 void foo(int addToData) { this->data += addToData; }

 int données;
};

...

Un an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // C'est la même que pour la ligne ci-dessus!
std::cout 

Le nombre de paramètres n'A::foo? Normalement, nous aurions dire 1. Mais sous le capot, toto prend vraiment 2. Regarder A::foo définition, il a besoin d'une instance spécifique d'Un pour le " ce " pointeur pour être significatif (le compilateur doit savoir ce qu'est 'ce' est). La façon dont vous habituellement spécifier ce que vous voulez 'ce' est par le biais de la syntaxe MyObject.MyMemberFunction(). Mais ce n'est que sucre syntaxique pour le passage de l'adresse de MyObject comme premier paramètre de MyMemberFunction. De même, lorsque nous déclarons membre des fonctions à l'intérieur des définitions de classe nous ne mettons pas le " il " dans la liste des paramètres, mais c'est juste un cadeau de la langue designers pour enregistrer la saisie. Au lieu de cela, vous devez spécifier qu'une fonction membre statique de refuser automatiquement l'obtention de l'extra " ce paramètre. Si le compilateur C++ traduit l'exemple ci-dessus en code C (à l'origine, le compilateur C++ effectivement travaillé de cette façon), il serait sans doute écrire quelque chose comme ceci:


struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

Revenir à votre exemple, il y a maintenant un problème évident. 'Init' veut un pointeur vers une fonction qui prend un paramètre. Mais &CLoggersInfra::RedundencyManagerCallBack est un pointeur vers une fonction qui prend deux paramètres, il est normal de paramètre et le secret 'de ce paramètre. Donc pourquoi vous obtenez toujours une erreur du compilateur (comme une note de côté: Si vous avez déjà utilisé Python, ce genre de confusion est pourquoi un 'soi' le paramètre est requis pour toutes les fonctions de membre).

Le verbose façon de gérer cela est de créer un objet spécial qui contient un pointeur vers l'instance que vous souhaitez et a une fonction membre appelé quelque chose comme " run " ou "exécuter" (ou surcharge l' '()' opérateur) qui prend les paramètres pour la fonction de membre, et appelle simplement la fonction de membre avec ces paramètres sur la stockées instance. Mais ce serait vous obliger à changer "Init" pour prendre votre objet spécial, plutôt que de les raw d'un pointeur de fonction, et il semble Init est quelqu'un d'autre code. Et de faire une classe spéciale pour chaque fois que ce problème va conduire à l'augmentation du code.

Alors maintenant, enfin, la bonne solution, boost::bind et boost::function, la documentation pour chaque que vous pouvez trouver ici:

boost::bind docs, boost::function docs

boost::bind vous permettra de prendre une fonction, et un paramètre de la fonction, et de faire une nouvelle fonction lorsque ce paramètre est "verrouillé" en place. Donc, si j'ai une fonction qui additionne deux nombres entiers, je peux utiliser boost::bind pour faire une nouvelle fonction où l'un des paramètres est verrouillé à-dire 5. Cette nouvelle fonction permettra de prendre uniquement un paramètre de type entier, et toujours ajouter 5 spécialement pour elle. En utilisant cette technique, vous pouvez "verrouiller" caché " de ce paramètre à une instance de classe, et de créer une nouvelle fonction qui ne prend qu'un seul paramètre, tout comme vous le voulez (notez que le paramètre masqué est toujours le premier paramètre, et les paramètres normaux viennent dans l'ordre après elle). Regardez les boost::bind docs pour des exemples, ils ont même discuter spécifiquement de l'utiliser pour les fonctions de membres. Techniquement, il est une fonction standard appelé std::bind1st que vous pourriez utiliser, mais les boost::bind est plus général.

Bien sûr, il ya juste un hic. boost::bind fera un joli boost::function pour vous, mais ce n'est toujours pas techniquement raw d'un pointeur de fonction comme Init veut sans doute. Heureusement, boost fournit un moyen de convertir les boost::function du raw pointeurs, comme documenté sur StackOverflow ici. Comment il met en œuvre ce qui est au-delà de la portée de cette réponse, mais c'est intéressant aussi.

Ne vous inquiétez pas si cela vous semble ridiculement dur -- votre question recoupe plusieurs de C++de coins sombres, et boost::bind est incroyablement utile une fois que vous apprendre.

70voto

Johannes Schaub - litb Points 256113

Comme vous venez de le montrer, votre fonction init accepte une fonction non membre. alors faites comme ça:

 static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}
 

et appelez Init avec

 m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);
 

Cela fonctionne car un pointeur de fonction sur une fonction membre statique n'est pas un pointeur de fonction membre et peut donc être traité comme un simple pointeur sur une fonction libre.

3voto

Konrad Rudolph Points 231505

Quel argument prend Init ? Quel est le nouveau message d'erreur?

Les pointeurs de méthodes en C ++ sont un peu difficiles à utiliser. Outre le pointeur de méthode lui-même, vous devez également fournir un pointeur d'instance (dans votre cas, this ). Peut-être que Init attend comme un argument séparé?

3voto

e.James Points 51680

Est - m_cRedundencyManager mesure d'utiliser les fonctions de membres? La plupart des rappels sont configurés pour utiliser des fonctions ou des fonctions membres statiques. Jetez un oeil à cette page en C++ FAQ Lite pour plus d'informations.

Mise à jour: la déclaration de La fonction que vous avez fournies montre que m_cRedundencyManager s'attend à une fonction de la forme: void yourCallbackFunction(int, void *). Les fonctions de membres sont donc inacceptables comme des callbacks dans ce cas. Une fonction membre statique peut fonctionner, mais si c'est inacceptable dans votre cas, le code suivant fonctionne également. Note qu'il utilise un mal exprimés, à partir d' void *.


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

3voto

jalf Points 142628

Un pointeur sur une fonction membre de la classe n’est pas identique à un pointeur sur une fonction. Un membre de la classe prend un argument supplémentaire implicite (le pointeur this ) et utilise une convention d'appel différente.

Si votre API attend une fonction de rappel non membre, c'est ce que vous devez lui transmettre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X