Jak mogę przekazać funkcję członkowską, w przypadku której oczekuje się bezpłatnej funkcji?


122

Pytanie jest następujące: rozważ ten fragment kodu:

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

Jak mogę korzystać z a„s aClass::testjako argument function1? Utknąłem w tym.

Chciałbym uzyskać dostęp do członka klasy.


1
Spójrz na tę odpowiedź stackoverflow.com/questions/2402579/ ... a także na często zadawane
amdn

16
To absolutnie nie jest duplikatem (a przynajmniej nie konkretnego pytania, które jest powiązane). To pytanie dotyczy tego, jak zadeklarować element członkowski, który jest wskaźnikiem do funkcji; chodzi o przekazanie wskaźnika do niestatycznej funkcji składowej jako parametru.
CarLuva

Odpowiedzi:


145

Nie ma nic złego w używaniu wskaźników funkcji. Jednak wskaźniki do niestatycznych funkcji składowych nie są podobne do zwykłych wskaźników do funkcji: funkcje składowe muszą być wywoływane w obiekcie, który jest przekazywany jako niejawny argument funkcji. Zatem podpis twojej funkcji członkowskiej powyżej to

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

zamiast typu, którego próbujesz użyć

void (*)(int, int)

Jedno podejście mogłoby polegać na utworzeniu funkcji składowej, staticw którym to przypadku nie wymaga ona wywołania żadnego obiektu i można jej użyć z typem void (*)(int, int).

Jeśli potrzebujesz dostępu do dowolnego niestatycznego elementu członkowskiego swojej klasy i musisz trzymać się wskaźników do funkcji, np. Ponieważ funkcja jest częścią interfejsu C, najlepszą opcją jest zawsze przekazywanie void*do funkcji a, przyjmując wskaźniki do funkcji i wywołując Twój element członkowski za pośrednictwem funkcji przekazującej, która uzyskuje obiekt z, void*a następnie wywołuje funkcję składową.

W odpowiednim interfejsie C ++ możesz rzucić okiem na to, aby Twoja funkcja pobierała argument oparty na szablonie dla obiektów funkcji, aby używały dowolnych typów klas. Jeśli używanie szablonu interfejsu jest niepożądane, powinieneś użyć czegoś takiego std::function<void(int, int)>: możesz utworzyć dla nich odpowiednio wywoływalny obiekt funkcji, np std::bind(). Using .

Podejścia z bezpiecznym typem przy użyciu argumentu szablonu dla typu klasy lub odpowiedniego std::function<...>są lepsze niż użycie void*interfejsu, ponieważ eliminują potencjalne błędy wynikające z rzutowania na niewłaściwy typ.

Aby wyjaśnić, jak używać wskaźnika funkcji do wywoływania funkcji składowej, oto przykład:

// 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, podoba mi się ta odpowiedź! Czy możesz sprecyzować, co masz na myśli mówiąc „zadzwoń do swojego członka poprzez funkcję przekierowującą, która pobiera obiekt z void *, a następnie wywołuje funkcję składową”, lub udostępnij użyteczny link do niego? Dzięki
Jorge Leitao

Myślę, że rozumiem. (Edytowałem Twój post) Dziękuję za wyjaśnienie i przykład, bardzo pomocne. Dla potwierdzenia: dla każdej funkcji członkowskiej, którą chcę wskazać, muszę utworzyć spedytora. Dobrze?
Jorge Leitao

Cóż, tak, w pewnym sensie. W zależności od tego, jak efektywnie używasz szablonów, możesz uciec od tworzenia szablonów przekazywania, które mogą współpracować z różnymi klasami i funkcjami członków. Jak to zrobić, to chyba osobna funkcja ;-)
Dietmar Kühl

1
Nie podoba mi się ta odpowiedź, ponieważ używa void*, co oznacza, że ​​możesz uzyskać bardzo nieprzyjemne błędy, ponieważ nie jest już wpisywana jako sprawdzona.
Superlokkus

@Superlokkus: czy możesz nas oświecić alternatywą?
Dietmar Kühl

89

Odpowiedź @Pete Becker jest w porządku, ale możesz to również zrobić bez przekazywania classinstancji jako jawnego parametru function1w 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
Jest void function1(std::function<void(int, int)>)poprawne?
Deqing

2
Musisz nadać argumentowi funkcji nazwę zmiennej, a nazwa zmiennej jest tym, co faktycznie przekazujesz. A więc: void function1(std::function<void(int, int)> functionToCall)a potem functionToCall(1,1);. Próbowałem edytować odpowiedź, ale ktoś odrzucił ją jako bezsensowną z jakiegoś powodu. Zobaczymy, czy w pewnym momencie zostanie on przegłosowany.
Inżynier Dorky

1
@DorkyEngineer To dość dziwne, myślę, że musisz mieć rację, ale nie wiem, jak ten błąd mógł pozostać niezauważony przez tak długi czas. W każdym razie zredagowałem teraz odpowiedź.
Matt Phillips,

2
Znalazłem ten post mówiący, że istnieje poważny spadek wydajności od std :: function.
kevin

2
@kevin, możesz poprawić swój komentarz, ponieważ odpowiedzi na ten post wskazywały na błąd w teście porównawczym.
Jorge Leitao

54

Wskaźnik do funkcji składowej różni się od wskaźnika do funkcji. Aby użyć funkcji składowej za pośrednictwem wskaźnika, potrzebujesz wskaźnika do niej (oczywiście) i obiektu, do którego chcesz ją zastosować. Więc odpowiednia wersja function1będzie

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

i nazwać to:

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

1
Dziękuję Ci bardzo. Właśnie dowiedziałem się, że tutaj liczą się nawiasy: function1 (& (aClass :: test), a) działa z MSVC2013, ale nie z gcc. gcc potrzebuje znaku & bezpośrednio przed nazwą klasy (co
wydaje

Myślałem, że functionw void (aClass::*function)(int, int)był typem, ponieważ jest to typ w typedef void (aClass::*function)(int, int).
Olumide

@Olumide - typedef int X;definiuje typ; int X;tworzy obiekt.
Pete Becker

11

Od 2011, jeśli możesz to zmienić function1, zrób to w ten sposób:

#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);
    });
}

( demo na żywo )

Zauważ również, że poprawiłem twoją zepsutą definicję obiektu ( aClass a();deklaruje funkcję).


2

Zadałem podobne pytanie ( otwarte ramy C ++ przechodzą nieważne z innych klas ), ale odpowiedź, którą znalazłem, była jaśniejsza, więc tutaj wyjaśnienie dla przyszłych rekordów:

łatwiej jest użyć funkcji std :: jak w:

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

a następnie zadzwoń jako:

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

lub jeszcze łatwiej:

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

gdzie tworzysz lambdę, która wywołuje obiekt przechwytujący go przez odniesienie


1
Biorąc pod uwagę, że jest to oznaczone CPP, myślę, że jest to najczystsze rozwiązanie. Moglibyśmy jednak użyć stałego odniesienia do std::functionin drawzamiast kopiowania go przy każdym wywołaniu.
Sohaib

2
W tym przykładzie nie należy używać symbolu zastępczego, ponieważ funkcja nie przyjmuje żadnych parametrów.
kroiz

1

Zrobiłem element członkowski jako statyczny i wszystko działa:

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

Nie tylko rozwiązuje główne pytanie „Jak mogę użyć aClass :: test jako argumentu funkcji function1?” ale także zaleca się unikanie modyfikowania prywatnych zmiennych klasy przy użyciu kodu zewnętrznego w stosunku do klasy
mathengineer,

1

Nie jestem pewien, dlaczego pominięto to niezwykle proste rozwiązanie:

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

Wynik:

1 - 1 = 0
1 + 1 = 2

0

Jeśli faktycznie nie trzeba korzystać z instancji a (czyli można je statyczne jak @mathengineer „s odpowiedzi ) można po prostu przejść w lambda non-capture. (które rozpadają się na wskaźnik funkcji)


#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


uwaga: jeśli aClassjest kosztowna w budowie lub ma efekt uboczny, może to nie być dobry sposób.


-1

Możesz teraz przestać walić głową. Oto opakowanie dla funkcji składowej do obsługi istniejących funkcji przyjmujących zwykłe funkcje C jako argumenty. thread_localDyrektywa jest tutaj kluczowa.

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;
}

Prosimy o skomentowanie wszelkich problemów związanych z tym podejściem.

Inne odpowiedzi nie wywołują istniejących zwykłych Cfunkcji: http://cpp.sh/8exun


3
Więc zamiast używać std :: bind lub lambda do zawijania instancji, polegasz na zmiennej globalnej. Nie widzę żadnej korzyści z tego podejścia w porównaniu z innymi odpowiedziami.
super

@ Super, Inne odpowiedzi nie mogą wywoływać istniejących funkcji przyjmujących zwykłe Cfunkcje jako argumenty.
Necktwi

2
Pytanie brzmi, jak wywołać funkcję składową. Przekazywanie darmowej funkcji już działa dla OP w pytaniu. Ty też nic nie przekazujesz. Są tu tylko zakodowane na stałe funkcje i globalny wskaźnik Foo_. Jak wyglądałaby ta skala, jeśli chcesz wywołać inną funkcję składową? Musiałbyś przepisać podstawowe funkcje lub użyć różnych dla każdego celu.
super
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.