Penunjuk fungsi ke fungsi anggota

92

Saya ingin menyiapkan penunjuk fungsi sebagai anggota kelas yang merupakan penunjuk ke fungsi lain di kelas yang sama. Alasan mengapa saya melakukan ini rumit.

Dalam contoh ini, saya ingin hasilnya menjadi "1"

class A {
public:
 int f();
 int (*x)();
}

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = a.f;
 printf("%d\n",a.x())
}

Tapi ini gagal dalam kompilasi. Mengapa?

Mike
sumber
@jww dan periksa jawaban CiroSantilli di pertanyaan itu, jawaban lain kurang lebih keluar topik. Pada dasarnya, hanya int (C :: * function_pointer_var) (int) = & C :: method; lalu C c; dan (c. * function_pointer_var) (2).
jw_

Jawaban:

160

Sintaksnya salah. Penunjuk anggota adalah kategori tipe yang berbeda dari penunjuk biasa. Pointer anggota harus digunakan bersama dengan objek kelasnya:

class A {
public:
 int f();
 int (A::*x)(); // <- declare by saying what class it is a pointer to
};

int A::f() {
 return 1;
}


int main() {
 A a;
 a.x = &A::f; // use the :: syntax
 printf("%d\n",(a.*(a.x))()); // use together with an object of its class
}

a.xbelum menjelaskan pada objek apa fungsi itu dipanggil. Itu hanya mengatakan bahwa Anda ingin menggunakan penunjuk yang disimpan di objek a. Mempersiapkan awaktu lain sebagai operan kiri ke .*operator akan memberi tahu compiler tentang objek apa yang akan memanggil fungsi tersebut.

Johannes Schaub - litb
sumber
Saya tahu ini sudah tua, tapi saya tidak mengerti penggunaan. (a.*a.x)()Mengapa (a.*x)()tidak berfungsi?
Gaurav Sehgal
3
@gau karena x tidak dalam cakupan
Johannes Schaub - litb
13
Saya harus melihat ini setiap kali saya menggunakannya juga. Sintaksnya membingungkan, tetapi masuk akal jika Anda memecahnya. a.xadalah pointer ke fungsi anggota kelas A. *a.xdereferensi pointer jadi sekarang referensi fungsi. a.(*a.x)"mengikat" fungsi ke sebuah instance (seperti a.f). (a.(*a.x))diperlukan untuk mengelompokkan sintaks yang kompleks ini, dan (a.(*a.x))()benar - benar memanggil metode atanpa argumen.
jwm
23

int (*x)()bukan penunjuk ke fungsi anggota. Sebuah pointer ke fungsi anggota ditulis seperti ini: int (A::*x)(void) = &A::f;.

Bertrand Marron
sumber
17

Panggil fungsi anggota pada perintah string

#include <iostream>
#include <string>


class A 
{
public: 
    void call();
private:
    void printH();
    void command(std::string a, std::string b, void (A::*func)());
};

void A::printH()
{
    std::cout<< "H\n";
}

void A::call()
{
    command("a","a", &A::printH);
}

void A::command(std::string a, std::string b, void (A::*func)())
{
    if(a == b)
    {
        (this->*func)();
    }
}

int main()
{
    A a;
    a.call();
    return 0;
}

Perhatikan (this->*func)();dan cara mendeklarasikan function pointer dengan nama classvoid (A::*func)()

Heto
sumber
11

Anda perlu menggunakan penunjuk ke fungsi anggota, bukan hanya penunjuk ke fungsi.

class A { 
    int f() { return 1; }
public:
    int (A::*x)();

    A() : x(&A::f) {}
};

int main() { 
   A a;
   std::cout << (a.*a.x)();
   return 0;
}
Jerry Coffin
sumber
3

Meskipun ini didasarkan pada jawaban yang sangat bagus di tempat lain di halaman ini, saya memiliki kasus penggunaan yang tidak sepenuhnya diselesaikan oleh mereka; untuk vektor pointer ke fungsi lakukan hal berikut:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>

class A{
public:
  typedef vector<int> (A::*AFunc)(int I1,int I2);
  vector<AFunc> FuncList;
  inline int Subtract(int I1,int I2){return I1-I2;};
  inline int Add(int I1,int I2){return I1+I2;};
  ...
  void Populate();
  void ExecuteAll();
};

void A::Populate(){
    FuncList.push_back(&A::Subtract);
    FuncList.push_back(&A::Add);
    ...
}

void A::ExecuteAll(){
  int In1=1,In2=2,Out=0;
  for(size_t FuncId=0;FuncId<FuncList.size();FuncId++){
    Out=(this->*FuncList[FuncId])(In1,In2);
    printf("Function %ld output %d\n",FuncId,Out);
  }
}

int main(){
  A Demo;
  Demo.Populate();
  Demo.ExecuteAll();
  return 0;
}

Sesuatu seperti ini berguna jika Anda menulis juru bahasa perintah dengan fungsi terindeks yang perlu digabungkan dengan sintaks parameter dan petunjuk bantuan, dll. Mungkin juga berguna dalam menu.

Burung hantu
sumber
1
Seperti yang didefinisikan, AFunc adalah penunjuk ke fungsi anggota yang mengambil dua int dan mengembalikan vektor int. Tapi para anggota menunjuk untuk kembali , kan? Saya pikir pernyataan typedef seharusnya typedef int (A::*AFunc)(int I1,int I2);
riderBill
3

Meskipun Anda sayangnya tidak dapat mengonversi penunjuk fungsi anggota yang sudah ada menjadi penunjuk fungsi biasa, Anda dapat membuat template fungsi adaptor dengan cara yang cukup mudah yang membungkus penunjuk fungsi anggota yang dikenal pada waktu kompilasi dalam fungsi normal seperti ini:

template <class Type>
struct member_function;

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...)>
{
    template <Ret(Type::*Func)(Args...)>
    static Ret adapter(Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

template <class Type, class Ret, class... Args>
struct member_function<Ret(Type::*)(Args...) const>
{
    template <Ret(Type::*Func)(Args...) const>
    static Ret adapter(const Type &obj, Args&&... args)
    {
        return (obj.*Func)(std::forward<Args>(args)...);
    }
};

 

int (*func)(A&) = &member_function<decltype(&A::f)>::adapter<&A::f>;

Perhatikan bahwa untuk memanggil fungsi anggota, turunan dari Aharus disediakan.

IllidanS4 mendukung Monica
sumber
Anda telah menginspirasi saya, @ IllidanS4. Lihat jawabanku. +1
memtha
1

Berdasarkan jawaban @ IllidanS4, saya telah membuat kelas template yang memungkinkan hampir semua fungsi anggota dengan argumen yang telah ditentukan dan instance kelas untuk diteruskan dengan referensi untuk dipanggil nanti.



template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs&&... rargs) = 0;
    //virtual RET call() = 0;
};

template<class T, class RET, class... RArgs> class CallbackCalltimeArgs : public Callback_t<RET, RArgs...> {
public:
    T * owner;
    RET(T::*x)(RArgs...);
    RET call(RArgs&&... rargs) {
        return (*owner.*(x))(std::forward<RArgs>(rargs)...);
    };
    CallbackCalltimeArgs(T* t, RET(T::*x)(RArgs...)) : owner(t), x(x) {}
};

template<class T, class RET, class... Args> class CallbackCreattimeArgs : public Callback_t<RET> {
public:
    T* owner;
    RET(T::*x)(Args...);
    RET call() {
        return (*owner.*(x))(std::get<Args&&>(args)...);
    };
    std::tuple<Args&&...> args;
    CallbackCreattimeArgs(T* t, RET(T::*x)(Args...), Args&&... args) : owner(t), x(x),
        args(std::tuple<Args&&...>(std::forward<Args>(args)...)) {}
};

Tes / contoh:

class container {
public:
    static void printFrom(container* c) { c->print(); };
    container(int data) : data(data) {};
    ~container() {};
    void print() { printf("%d\n", data); };
    void printTo(FILE* f) { fprintf(f, "%d\n", data); };
    void printWith(int arg) { printf("%d:%d\n", data, arg); };
private:
    int data;
};

int main() {
    container c1(1), c2(20);
    CallbackCreattimeArgs<container, void> f1(&c1, &container::print);
    Callback_t<void>* fp1 = &f1;
    fp1->call();//1
    CallbackCreattimeArgs<container, void, FILE*> f2(&c2, &container::printTo, stdout);
    Callback_t<void>* fp2 = &f2;
    fp2->call();//20
    CallbackCalltimeArgs<container, void, int> f3(&c2, &container::printWith);
    Callback_t<void, int>* fp3 = &f3;
    fp3->call(15);//20:15
}

Jelas, ini hanya akan berfungsi jika argumen yang diberikan dan kelas pemilik masih valid. Sejauh keterbacaan ... maafkan saya.

Edit: hapus malloc yang tidak perlu dengan membuat tuple penyimpanan normal. Menambahkan jenis yang diwariskan untuk referensi. Menambahkan opsi untuk memberikan semua argumen pada waktu panggilan sebagai gantinya. Sekarang bekerja untuk memiliki keduanya ....

Edit 2: Seperti yang dijanjikan, keduanya. Hanya batasan (yang saya lihat) adalah bahwa argumen yang telah ditentukan harus datang sebelum argumen yang diberikan runtime dalam fungsi callback. Terima kasih kepada @Chipster untuk beberapa bantuan terkait kepatuhan gcc. Ini berfungsi pada gcc di ubuntu dan studio visual di windows.

#ifdef _WIN32
#define wintypename typename
#else
#define wintypename
#endif

template<class RET, class... RArgs> class Callback_t {
public:
    virtual RET call(RArgs... rargs) = 0;
    virtual ~Callback_t() = default;
};

template<class RET, class... RArgs> class CallbackFactory {
private:
    template<class T, class... CArgs> class Callback : public Callback_t<RET, RArgs...> {
    private:
        T * owner;
        RET(T::*x)(CArgs..., RArgs...);
        std::tuple<CArgs...> cargs;
        RET call(RArgs... rargs) {
            return (*owner.*(x))(std::get<CArgs>(cargs)..., rargs...);
        };
    public:
        Callback(T* t, RET(T::*x)(CArgs..., RArgs...), CArgs... pda);
        ~Callback() {};
    };
public:
    template<class U, class... CArgs> static Callback_t<RET, RArgs...>* make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...));
};
template<class RET2, class... RArgs2> template<class T2, class... CArgs2> CallbackFactory<RET2, RArgs2...>::Callback<T2, CArgs2...>::Callback(T2* t, RET2(T2::*x)(CArgs2..., RArgs2...), CArgs2... pda) : x(x), owner(t), cargs(std::forward<CArgs2>(pda)...) {}
template<class RET, class... RArgs> template<class U, class... CArgs> Callback_t<RET, RArgs...>* CallbackFactory<RET, RArgs...>::make(U* owner, CArgs... cargs, RET(U::*func)(CArgs..., RArgs...)) {
    return new wintypename CallbackFactory<RET, RArgs...>::Callback<U, CArgs...>(owner, func, std::forward<CArgs>(cargs)...);
}
memtha
sumber