Pertanyaan Bagaimana saya bisa melewati fungsi pointer anggota ke fungsi yang mengambil fungsi pointer biasa?


Saya memiliki kelas pemain yang terlihat seperti ini (dilucuti ke apa yang dibutuhkan untuk masalah ini):

class Player
{
    public:
        Player();
        ~Player();

        void kill();
        void death();
        void reset();
};

Itu kill(), death(), dan reset() fungsi terlihat seperti ini:

void Player::kill()
{
    void (*dPtr)() = &death;

    Game::idle(dPtr, 48);
}

void Player::death()
{
    reset();
}

void Player::reset()
{
    //resets
}

Fungsi menganggur adalah fungsi memeber statis dari Game, yang mengambil fungsi pointer dan n integer, dan memanggil fungsi setelah n centang. Berikut fungsinya, penerapannya tidak masalah:

class Game {
    static void idle(void (*)(), int);
};

Kode ini memberi saya kesalahan:

ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.  Say '&Player::death' [-fpermissive]

Jadi saya mengubah garis dari

    void (*dPtr)() = &death;

untuk

    void (Player::*dPtr)() = &Player::death;

untuk mengatasi masalah itu. Tapi kemudian panggilan saya ke fungsi idle tidak benar, karena dibutuhkan penunjuk fungsi biasa, dan saya lewat di fungsi anggota pointer, dan dengan demikian memberi saya kesalahan:

no matching function for call to 'Game::idle(void (Player::*&)(), int)'

Jadi pertanyaan saya adalah: Bagaimana saya bisa melewati fungsi pointer anggota Player::*dPtr ke dalam fungsi yang tidak aktif, yang mengambil a void (*)() sebagai argumen?

Atau adakah cara lain untuk menyelesaikan kesalahan saya sebelumnya yang melarang saya mengambil alamat dari fungsi anggota yang tidak memenuhi syarat untuk membentuk penunjuk ke fungsi anggota?


4
2018-03-15 20:40


asal


Jawaban:


Untuk memanggil fungsi anggota melalui penunjuk, Anda perlu dua pointer: pointer ke fungsi itu sendiri, dan pointer ke objek yang akan this. Anda Game::idle API tidak mendukung penggunaan ini. Anda perlu mengubahnya sehingga melewati setidaknya satu argumen (secara konvensional tipe void *) ke panggilan balik. Kemudian Anda dapat menggunakan pola berikut:

struct Player
{
        // ...
        void kill();

        // ...
        static void call_kill(void *self);
};

void Player::call_kill(void *self)
{
    static_cast<Player *>(self)->kill();
}

struct Game
{
    static void idle(void (*)(void *), void *, int);
};

void Game::idle(void (*callback)(void *), void *arg, int ticks)
{
    // ...
    callback(arg);
    // ...
}

void kill_player_delayed(Player *p, int ticks)
{
    Game::idle(Player::call_kill, static_cast<void *>(p), ticks);
}

Anda harus menulis statis call_X metode untuk setiap metode instance X Anda ingin menelepon.


Sebuah pendekatan alternatif, yang bisa dibilang lebih C ++ - idiomatis dan fleksibel, dan melibatkan kode tertulis yang kurang eksplisit, tetapi memiliki biaya waktu proses yang lebih tinggi (tiga panggilan fungsi tidak langsung dan siklus bebas alokasi bebas per permintaan, bukannya panggilan fungsi tidak langsung tunggal ), adalah memiliki Game::idle mengambil objek dari kelas tertentu, dengan metode panggilan balik virtual. Kelas itu kemudian diberi subclass template yang dapat memanggil apa pun yang diimplementasikan operator(), seperti hasil dari std::bind.

struct Runnable { virtual ~Runnable(); virtual void invoke() = 0; };

template <typename T> struct TRunnable : Runnable {
    TRunnable(T target) : target(target) {}
    void invoke() { target(); }
private:
    T target;
};
template <typename T> TRunnable<T>* make_Runnable(T obj)
{ return new TRunnable<T>(obj); }

struct Game
{
    static void idle(Runnable *, int);
};

void Game::idle(Runnable *r, int ticks)
{
    // ...
    r->invoke();
    delete r;
    // ...
}

struct Player
{
    // ...
    void kill();
    // ...
};

void kill_player_delayed(Player *p, int ticks)
{
    Game::idle(make_Runnable(std::bind(&Player::kill, p)), ticks);
}

Anda tidak bisa membuatnya Game::idle ambil hasil std::bind langsung karena jenis objek tidak ditentukan (dan bervariasi tergantung pada cara Anda memanggil std::bind), jadi itu hanya bisa digunakan sebagai argumen untuk a template panggilan fungsi. Metode virtual memanggil kelas adaptor adalah satu-satunya cara untuk menyimpannya Game::idle dikompilasi out-of-line dan masih membiarkannya menggunakan objek panggilan terbatas.

Dalam pendekatan apa pun, berhati-hatilah dengan masalah usia objek. Khususnya, jika Game::idle tidak memanggil panggilannya sebelum kembali, kamu butuh pastikan bahwa kedua objek asli, dan (dalam pendekatan kedua) objek dikembalikan oleh make_Runnable bertahan hingga callback diaktifkan. Ini sebabnya make_Runnable menggunakan new.


2
2018-03-15 20:52



Jawaban lain menyebutkan bahwa Anda membutuhkan dua petunjuk. Namun C ++ sudah dilengkapi dengan kontainer untuk melakukan hal ini, jadi itu akan membuat kode Anda jauh lebih mudah untuk menggunakannya. (Dalam C ++ 03, beberapa std::item di bawah ini std::tr1::).

Kode sampel:

#include <iostream>
#include <functional>

struct Game 
{ 
    static void idle( std::function<void()> func, int x )
        { std::cout << "x = " << x << "\n"; func(); }
};

struct Player
{
     void death() { std::cout << "player.death\n"; }
     void kill() { Game::idle( std::bind(&Player::death, this), 48 ); }
};

int main()
{
    Player p;
    p.kill();
}

Catatan seumur hidup: std::bind mengikat berdasarkan nilai. Menggunakan *this berarti salinan dari Player dibuat dan disimpan dalam std::function objek, disalin dengan seperlunya.

Menggunakan this berarti objek fungsi menyimpan pointer, jadi jika Anda benar-benar menyimpan objek fungsi Game::idle kamu harus berhati-hati bahwa ini Player tidak dihancurkan sebelum menghapus objek fungsi ini dari Game::idledaftar.


4
2018-03-16 00:08



Karena saya benar-benar tidak suka jawaban yang dilemparkan void*'s ke objek lain (hampir tidak pernah diperlukan dalam C ++!) dan tidak ada yang memposting jawaban menggunakan saran di komentar saya akan menyarankan ini.

Gunakan jenis templated untuk callback Anda!

Seperti ini:

class Game{
    template<typename Func>
    static void idle(Func &&func, int i){
        // game stuff
        func();
        // other game stuff
    }
};

Maka Anda tidak kehilangan semua jenis keamanan Anda (casting void*) dan itu harus menjadi solusi tercepat.


Juga, di mana Anda menugaskan penunjuk fungsi, Anda dapat mengubah kode agar jauh lebih mudah dibaca dalam kasus ini:

void Player::kill(){
    Game::idle([this](){this->death();}, 48);
}

Yang jauh lebih baik daripada harus menulis tipe pointer fungsi yang benar.


1
2018-03-15 23:37



Anda tidak dapat melakukan itu hanya karena penunjuk ke fungsi [statis] adalah ukuran penunjuk tunggal void*. Sebaliknya fungsi anggota memerlukan informasi lebih lanjut, mis. dua pointer: satu untuk this dan satu lagi untuk fungsi itu sendiri sehingga fungsi pointer anggota memiliki sizeof > sizeof(void*).

Karena itu Anda memiliki dua opsi:

  1. untuk mengubah tanda tangan dari idle Anda () ke ini void idle(void (*)(), void*, int); jadi kamu akan bisa lulus this entah bagaimana.
  2. Atau buatlah variabel statis yang akan dipegang this penunjuk. Tetapi itu mengasumsikan bahwa hanya satu death() dapat berada di antrian menganggur pada waktu tertentu.

1) adalah apa yang biasanya dilakukan orang dalam kasus seperti itu.


0
2018-03-15 22:24