Comment puis-je passer une fonction membre où une fonction gratuite est attendue?


122

La question est la suivante: considérez ce morceau de code:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

Comment puis-je utiliser les a's aClass::testcomme argument pour function1? Je suis coincé à faire ça.

Je souhaite accéder à un membre de la classe.


1
Jetez un œil à cette réponse stackoverflow.com/questions/2402579/… et aussi cette FAQ C ++ parashift.com/c++-faq/pointers-to-members.html
amdn

16
Ce n'est absolument pas un doublon (du moins pas de la question particulière qui est liée). Cette question porte sur la façon de déclarer un membre qui est un pointeur vers une fonction; il s'agit de savoir comment passer un pointeur vers une fonction membre non statique en tant que paramètre.
CarLuva

Réponses:


144

Il n'y a rien de mal à utiliser des pointeurs de fonction. Cependant, les pointeurs vers des fonctions membres non statiques ne sont pas comme les pointeurs de fonction normaux: les fonctions membres doivent être appelées sur un objet qui est passé comme argument implicite à la fonction. La signature de votre fonction membre ci-dessus est donc

void (aClass::*)(int, int)

plutôt que le type que vous essayez d'utiliser

void (*)(int, int)

Une approche pourrait consister à faire fonctionner le membre, staticauquel cas il ne nécessite aucun objet pour être appelé et vous pouvez l'utiliser avec le type void (*)(int, int).

Si vous avez besoin d'accéder à un membre non statique de votre classe et que vous devez vous en tenir aux pointeurs de fonction, par exemple, parce que la fonction fait partie d'une interface C, votre meilleure option est de toujours passer un void*à votre fonction en prenant des pointeurs de fonction et d'appeler votre membre via une fonction de transfert qui obtient un objet de la void*puis appelle la fonction membre.

Dans une interface C ++ appropriée, vous voudrez peut-être jeter un œil à la manière dont votre fonction prend un argument basé sur un modèle pour que les objets fonction utilisent des types de classe arbitraires. Si l'utilisation d'une interface basée sur un modèle n'est pas souhaitable, vous devez utiliser quelque chose comme std::function<void(int, int)>: vous pouvez créer un objet fonction convenablement appelable pour ceux-ci, par exemple en utilisant std::bind().

Les approches de type sécurisé utilisant un argument de modèle pour le type de classe ou un type approprié std::function<...>sont préférables à l'utilisation d'une void*interface car elles suppriment le potentiel d'erreurs dues à une conversion vers le type incorrect.

Pour clarifier comment utiliser un pointeur de fonction pour appeler une fonction membre, voici un exemple:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}

Ok, j'aime cette réponse! Pouvez-vous préciser ce que vous entendez par "appeler votre membre via une fonction de transfert qui obtient un objet du void * puis appelle la fonction membre", ou partager un lien utile vers celui-ci? Merci
Jorge Leitao

Je crois que j'ai compris. (J'ai édité votre message) Merci pour l'explication et l'exemple, vraiment utile. Juste pour confirmer: pour chaque fonction membre que je veux pointer, je dois faire un transitaire. Droite?
Jorge Leitao

Eh bien, oui, en quelque sorte. En fonction de l'efficacité de votre utilisation des modèles, vous pouvez créer des modèles de transfert qui peuvent fonctionner avec différentes classes et fonctions membres. Comment faire cela serait une fonction distincte, je pense ;-)
Dietmar Kühl

1
Je n'aime pas cette réponse car elle utilise void*, ce qui signifie que vous pouvez obtenir des bugs très méchants, car elle n'est plus vérifiée.
Superlokkus

@Superlokkus: pouvez-vous nous éclairer avec une alternative?
Dietmar Kühl

88

La réponse de @Pete Becker est bonne, mais vous pouvez également le faire sans passer l' classinstance en tant que paramètre explicite function1dans C ++ 11:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}

1
Est-ce void function1(std::function<void(int, int)>)correct?
Deqing le

2
Vous devez donner à l'argument de la fonction un nom de variable, puis le nom de la variable est ce que vous passez réellement. Alors: void function1(std::function<void(int, int)> functionToCall)et puis functionToCall(1,1);. J'ai essayé de modifier la réponse mais quelqu'un l'a rejetée car elle n'avait aucun sens pour une raison quelconque. Nous verrons s'il obtient un vote positif à un moment donné.
Dorky Engineer

1
@DorkyEngineer C'est assez bizarre, je pense que vous devez avoir raison mais je ne sais pas comment cette erreur a pu passer inaperçue pendant si longtemps. Quoi qu'il en soit, j'ai modifié la réponse maintenant.
Matt Phillips

2
J'ai trouvé cet article disant qu'il y avait une grave pénalité de performance de std :: function.
kevin

2
@kevin, vous voudrez peut-être réviser votre commentaire, car les réponses à ce message montraient une faille dans le repère.
Jorge Leitao le

54

Un pointeur vers une fonction membre est différent d'un pointeur vers une fonction. Pour utiliser une fonction membre via un pointeur, vous avez besoin d'un pointeur vers elle (évidemment) et d'un objet auquel l'appliquer. La version appropriée de function1serait donc

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

et pour l'appeler:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

1
Merci beaucoup. Je viens de découvrir que les parenthèses comptent ici: function1 (& (aClass :: test), a) fonctionne avec MSVC2013 mais pas avec gcc. gcc a besoin du & directement devant le nom de la classe (ce que je trouve déroutant, car l'opérateur & prend l'adresse de la fonction, pas celle de la classe)
kritzel_sw

Je pensais functionen void (aClass::*function)(int, int)était un type, à cause de cela est un type dans typedef void (aClass::*function)(int, int).
Olumide

@Olumide - typedef int X;définit un type; int X;crée un objet.
Pete Becker

11

Depuis 2011, si vous pouvez changer function1, faites-le, comme ceci:

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

( démo en direct )

Notez également que j'ai corrigé votre définition d'objet cassée ( aClass a();déclare une fonction).


2

J'ai posé une question similaire ( openframeworks C ++ passant vide des autres classes ) mais la réponse que j'ai trouvée était plus claire, alors voici l'explication pour les enregistrements futurs:

il est plus facile d'utiliser std :: function comme dans:

 void draw(int grid, std::function<void()> element)

puis appelez comme:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

ou encore plus simple:

  grid.draw(12, [&]{a.draw()});

où vous créez un lambda qui appelle l'objet en le capturant par référence


1
Étant donné que cela est étiqueté CPP, je pense que c'est la solution la plus propre. Nous pourrions utiliser une référence constante à std::functionen drawsi au lieu de le copier sur chaque appel.
Sohaib

2
L'espace réservé ne doit pas être utilisé dans cet exemple car la fonction ne prend aucun paramètre.
kroiz le

1

J'ai fait fonctionner le membre comme statique et tout fonctionne:

#include <iostream>

class aClass
{
public:
    static void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}

Non seulement résoudre la question principale "Comment puis-je utiliser le aClass :: test comme argument de function1?" mais aussi il est recommandé d'éviter de modifier les variables privées de classe en utilisant du code externe à la classe
mathengineer

1

Je ne sais pas pourquoi cette solution incroyablement simple a été ignorée:

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

Production:

1 - 1 = 0
1 + 1 = 2

0

Si vous n'avez pas vraiment besoin d'utiliser l'instance a (c'est-à-dire que vous pouvez la rendre statique comme la réponse de @mathengineer ), vous pouvez simplement passer un lambda sans capture. (qui désintègre le pointeur de fonction)


#include <iostream>

class aClass
{
public:
   void aTest(int a, int b)
   {
      printf("%d + %d = %d", a, b, a + b);
   }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

Wandbox


Remarque: si la aClassconstruction est coûteuse ou a des effets secondaires, cela peut ne pas être un bon moyen.


-1

Vous pouvez arrêter de vous cogner la tête maintenant. Voici le wrapper de la fonction membre pour prendre en charge les fonctions existantes prenant des fonctions C simples comme arguments. thread_localdirective est la clé ici.

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

Veuillez commenter tout problème lié à cette approche.

Les autres réponses ne parviennent pas à appeler lesC fonctions simples existantes : http://cpp.sh/8exun


3
Ainsi, au lieu d'utiliser std :: bind ou un lambda pour encapsuler l'instance, vous vous fiez à une variable globale. Je ne vois aucun avantage à cette approche par rapport aux autres réponses.
super

@super, les autres réponses ne peuvent pas appeler des fonctions existantes prenant des Cfonctions simples comme arguments.
Necktwi

2
La question est de savoir comment appeler une fonction membre. Passer une fonction gratuite fonctionne déjà pour OP dans la question. Vous ne transmettez rien non plus. Il n'y a ici que des fonctions codées en dur et un pointeur global Foo_. Comment cette échelle serait-elle si vous souhaitez appeler une fonction membre différente? Vous devrez réécrire les fonctions sous-jacentes ou utiliser des fonctions différentes pour chaque cible.
super
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.