Bagaimana saya bisa melewatkan fungsi anggota di mana fungsi gratis diharapkan?

122

Pertanyaannya adalah sebagai berikut: pertimbangkan potongan kode ini:

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

Bagaimana saya bisa menggunakan a's aClass::testsebagai argumen untukfunction1 ? Saya terjebak dalam melakukan ini.

Saya ingin mengakses anggota kelas.

Jorge Leitao
sumber
16
Ini sama sekali bukan duplikat (setidaknya bukan dari pertanyaan tertentu yang ditautkan). Pertanyaan itu adalah tentang bagaimana mendeklarasikan anggota yang merupakan penunjuk ke suatu fungsi; ini tentang cara meneruskan pointer ke fungsi anggota non-statis sebagai parameter.
CarLuva

Jawaban:

144

Tidak ada yang salah dengan menggunakan pointer fungsi. Namun, pointer ke fungsi anggota non-statis tidak seperti pointer fungsi normal: fungsi anggota perlu dipanggil pada objek yang diteruskan sebagai argumen implisit ke fungsi tersebut. Jadi, tanda tangan dari fungsi anggota Anda di atas adalah

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

daripada tipe yang Anda coba gunakan

void (*)(int, int)

Satu pendekatan dapat terdiri dari membuat fungsi anggota staticdalam hal ini tidak memerlukan objek apa pun untuk dipanggil dan Anda dapat menggunakannya dengan tipe void (*)(int, int).

Jika Anda perlu mengakses anggota non-statis kelas Anda dan Anda harus tetap menggunakan pointer fungsi, misalnya, karena fungsinya adalah bagian dari antarmuka C, pilihan terbaik Anda adalah selalu meneruskan a void*ke fungsi Anda dengan mengambil fungsi pointer dan memanggil anggota Anda melalui fungsi penerusan yang memperoleh objek dari void*dan kemudian memanggil fungsi anggota.

Dalam antarmuka C ++ yang tepat, Anda mungkin ingin melihat agar fungsi Anda mengambil argumen templated untuk objek fungsi agar menggunakan tipe kelas arbitrer. Jika menggunakan antarmuka templated tidak diinginkan, Anda harus menggunakan sesuatu seperti std::function<void(int, int)>: Anda dapat membuat objek fungsi yang dapat dipanggil yang sesuai untuk ini, misalnya menggunakan std::bind().

Pendekatan tipe-aman menggunakan argumen template untuk tipe kelas atau yang sesuai std::function<...>lebih disukai daripada menggunakan void*antarmuka karena mereka menghilangkan potensi kesalahan karena cast ke tipe yang salah.

Untuk memperjelas cara menggunakan penunjuk fungsi untuk memanggil fungsi anggota, berikut ini contohnya:

// 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);
}
Dietmar Kühl
sumber
Oke, saya suka jawaban ini! Bisakah Anda menjelaskan apa yang Anda maksud dengan "panggil anggota Anda melalui fungsi penerusan yang memperoleh objek dari void * dan kemudian memanggil fungsi anggota", atau membagikan tautan yang berguna ke sana? Terima kasih
Jorge Leitao
Saya pikir saya mengerti. (Saya sudah mengedit postingan Anda) Terima kasih atas penjelasan dan contohnya, sangat membantu. Sekadar konfirmasi: untuk setiap fungsi anggota yang ingin saya tunjuk, saya harus membuat forwarder. Baik?
Jorge Leitao
Ya, semacam itu. Bergantung pada seberapa efektif Anda menggunakan template, Anda dapat membuat template penerusan yang dapat bekerja dengan kelas dan fungsi anggota yang berbeda. Bagaimana melakukan ini akan menjadi fungsi terpisah, saya pikir ;-)
Dietmar Kühl
1
Saya tidak suka jawaban ini karena menggunakan void*, yang berarti Anda bisa mendapatkan bug yang sangat buruk, karena tidak diketik dicentang lagi.
Superlokkus
@Superlokkus: bisakah Anda mencerahkan kami dengan alternatif lain?
Dietmar Kühl
88

Jawaban @ Pete Becker baik-baik saja tetapi Anda juga dapat melakukannya tanpa meneruskan classinstance sebagai parameter eksplisit ke function1dalam 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;
}
Matt Phillips
sumber
1
Apakah void function1(std::function<void(int, int)>)benar?
Deqing
2
Anda perlu memberi argumen fungsi nama variabel dan kemudian nama variabel adalah apa yang sebenarnya Anda berikan. Jadi: void function1(std::function<void(int, int)> functionToCall)dan kemudian functionToCall(1,1);. Saya mencoba mengedit jawabannya tetapi seseorang menolaknya karena tidak masuk akal karena alasan tertentu. Kami akan melihat apakah itu mendapat suara positif di beberapa titik.
Dorky Engineer
1
@DorkyEngineer Itu cukup aneh, saya pikir Anda pasti benar, tetapi saya tidak tahu bagaimana kesalahan itu bisa begitu lama tidak diketahui. Bagaimanapun, saya telah mengedit jawabannya sekarang.
Matt Phillips
2
Saya menemukan posting ini mengatakan bahwa ada penalti kinerja yang parah dari std :: function.
kevin
2
@kevin, Anda mungkin ingin merevisi komentar Anda, karena jawaban posting itu menunjukkan cacat pada tolok ukurnya.
Jorge Leitao
54

Fungsi penunjuk ke anggota berbeda dari penunjuk ke fungsi. Untuk menggunakan fungsi anggota melalui penunjuk, Anda memerlukan penunjuk ke sana (jelas) dan objek untuk menerapkannya. Jadi versi yang sesuai function1adalah

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

dan menyebutnya:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);
Pete Becker
sumber
1
Terima kasih banyak. Saya baru tahu bahwa tanda kurung dihitung di sini: function1 (& (aClass :: test), a) berfungsi dengan MSVC2013 tetapi tidak dengan gcc. gcc membutuhkan & langsung di depan nama kelas (yang menurut saya membingungkan, karena operator & mengambil alamat fungsi, bukan kelas)
kritzel_sw
Saya pikir functionmasuk void (aClass::*function)(int, int)adalah tipe, karena itu adalah tipe dalam typedef void (aClass::*function)(int, int).
Olumide
@Olumide - typedef int X;mendefinisikan tipe; int X;menciptakan sebuah objek.
Pete Becker
11

Sejak 2011, jika Anda bisa berubah function1, lakukan seperti ini:

#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 langsung )

Perhatikan juga bahwa saya memperbaiki definisi objek Anda yang rusak ( aClass a();mendeklarasikan fungsi).

Balapan Ringan dalam Orbit
sumber
2

Saya mengajukan pertanyaan serupa ( C ++ openframeworks melewati batal dari kelas lain ) tetapi jawaban yang saya temukan lebih jelas jadi di sini penjelasan untuk catatan masa depan:

lebih mudah menggunakan std :: function seperti pada:

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

lalu panggil sebagai:

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

atau bahkan lebih mudah:

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

tempat Anda membuat lambda yang memanggil objek yang menangkapnya dengan referensi

Nicola Bertelloni
sumber
1
Mengingat ini diberi tag CPP, saya pikir ini adalah solusi terbersih. Kita bisa menggunakan referensi konstan ke std::functiondalam drawalih-alih menyalinnya di setiap panggilan.
Sohaib
2
Placeholder tidak boleh digunakan dalam contoh ini karena fungsinya tidak menggunakan parameter apa pun.
kroiz
1

Saya membuat fungsi anggota sebagai statis dan semuanya berfungsi:

#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;
}
mathengineer
sumber
Tidak hanya memecahkan pertanyaan utama "Bagaimana saya bisa menggunakan aClass :: test sebagai argumen untuk function1?" tetapi juga disarankan untuk menghindari modifikasi variabel privat kelas menggunakan kode eksternal ke kelas
mathengineer
1

Tidak yakin mengapa solusi yang sangat sederhana ini dilewatkan:

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

Keluaran:

1 - 1 = 0
1 + 1 = 2
hLk
sumber
0

Jika Anda benar-benar tidak perlu menggunakan instance a (misalnya, Anda dapat membuatnya statis seperti jawaban @mathengineer ) Anda cukup memasukkan lambda non-capture. (yang membusuk menjadi penunjuk fungsi)


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

Kotak tongkat sihir


Catatan: jika aClassmahal untuk dibangun atau memiliki efek samping, ini mungkin bukan cara yang baik.

apel apel
sumber
-1

Anda bisa berhenti membenturkan kepala sekarang. Berikut adalah pembungkus untuk fungsi anggota untuk mendukung fungsi yang ada yang menggunakan fungsi C biasa sebagai argumen. thread_localdirektif adalah kuncinya di sini.

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

Harap beri komentar tentang masalah apa pun dengan pendekatan ini.

Jawaban lain gagal memanggil fungsi biasa yang adaC : http://cpp.sh/8exun

Necktwi
sumber
3
Jadi, alih-alih menggunakan std :: bind atau lambda untuk membungkus instance, Anda mengandalkan variabel global. Saya tidak melihat keuntungan dari pendekatan ini dibandingkan dengan jawaban lainnya.
super
@super, Jawaban lain tidak dapat memanggil fungsi yang ada yang menggunakan Cfungsi biasa sebagai argumen.
Necktwi
2
Pertanyaannya adalah bagaimana memanggil fungsi anggota. Meneruskan fungsi gratis sudah berfungsi untuk OP dalam pertanyaan. Anda juga tidak mengirimkan apapun. Hanya ada fungsi kode keras di sini dan penunjuk Foo_ global. Bagaimana skala ini jika Anda ingin memanggil fungsi anggota yang berbeda? Anda harus menulis ulang fungsi yang mendasarinya, atau menggunakan fungsi yang berbeda untuk setiap target.
super