web-dev-qa-db-fra.com

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

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 utiliser le aClass::test de la a comme argument pour function1? Je suis coincé dans le faire.

J'aimerais avoir accès à un membre de la classe.

89
Jorge Leitão

Il n'y a rien de mal à utiliser les pointeurs de fonction. Cependant, les pointeurs vers des fonctions membres non statiques ne ressemblent pas aux pointeurs de fonction normaux: les fonctions membres doivent être appelées sur un objet qui est transmis en tant qu'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 solution pourrait consister à rendre la fonction membre static, auquel cas aucun objet n’est appelé et vous pouvez l’utiliser avec le type void (*)(int, int).

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

Dans une interface C++ appropriée, vous pouvez souhaiter que votre fonction prenne 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 devriez utiliser quelque chose comme std::function<void(int, int)>: vous pouvez créer un objet fonction convenable pour ces objets, 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 std::function<...> approprié sont préférables à l’utilisation d’une interface void* car elles suppriment le risque d’erreurs dues à une conversion vers un 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);
}
107
Dietmar Kühl

La réponse de @Pete Becker est correcte, mais vous pouvez également le faire sans passer l'instance class en tant que paramètre explicite à function1 en 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;
}
62
Matt Phillips

Un pointeur sur une fonction membre est différent d'un pointeur sur une fonction. Pour utiliser une fonction membre via un pointeur, vous avez besoin d'un pointeur sur celle-ci (évidemment) et d'un objet auquel l'appliquer. Donc, la version appropriée de function1 serait

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);
39
Pete Becker

Depuis 2011, si vous pouvez modifier function1, procédez comme suit:

#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é la définition de votre objet cassé (aClass a(); déclare une fonction).

2

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

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

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

puis appelez en tant que:

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

ou encore plus facile:

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

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

1

Vous pouvez arrêter de vous cogner la tête maintenant. Voici l'encapsuleur permettant à la fonction membre de prendre en charge les fonctions existantes en prenant les fonctions simples C comme arguments. La directive thread_local 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 avec cette approche.

D'autres réponses échouent dans l'appel des fonctions existantes plain C: http://cpp.sh/8exun

0
neckTwi

J'ai fait fonctionner le membre en tant que statique et toutes les œuvres:

#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;
}
0
mathengineer