简体   繁体   中英

How can I pass a member function pointer into a function that takes a regular function pointer?

I have a player class which looks like this (stripped down to what is needed for this problem):

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

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

The kill() , death() , and reset() functions look like this:

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

    Game::idle(dPtr, 48);
}

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

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

The idle function is a static memeber function of Game, which takes a function pointer and an integer n, and calls the function after n tick. Here is the function, the implementation shouldn't matter:

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

This code gives me the error:

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]

So I change the line from

    void (*dPtr)() = &death;

to

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

to solve that issue. But then my call to the idle function is incorrect, as it takes a regular function pointer, and I am passing in a member function pointer, and thus gives me the error:

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

So my question is: How can I pass the member function pointer Player::*dPtr into the idle function, which takes a void (*)() as an argument?

Or is there another way I can solve my previous error which forbids me from taking the address of an unqualified member function to form a pointer to a member function?

Another answer mentions that you need two pointers. However C++ already comes with containers for doing just this, so it would make your code a lot simpler to use those. (In C++03, some of the std:: items below were std::tr1:: ).

Sample code:

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

Lifetime note: std::bind binds by value. Using *this means a copy of the Player is made and stored in the std::function object, copied around with it as necessary.

Using this means the function object stores a pointer, so if you actually store the function object in Game::idle you must take care that this Player is not destroyed before removing this function object from Game::idle 's list.

To call a member function through a pointer, you need two pointers: the pointer to the function itself, and a pointer to an object to be this . Your Game::idle API does not support this usage. You need to change it so that it passes at least one argument (conventionally of type void * ) to the callback. Then you can use the following pattern:

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

You have to write a static call_X method for every instance method X you want to call.


An alternative approach, which is arguably more C++-idiomatic and flexible, and involves less explicitly written-out code, but has higher runtime costs (three indirect function calls and a heap allocate-free cycle per invocation, instead of a single indirect function call), is to have Game::idle take an object of a particular class, with a virtual callback method. That class is then given a template subclass that can call anything that implements operator() , such as the result of 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);
}

You cannot make Game::idle take the result of std::bind directly because that object's type is unspecified (and varies depending on how you call std::bind ), so it can only be used as an argument to a template function call. A virtual method call to an adapter class is the only way to keep Game::idle compiled out-of-line and still let it use bound-call objects.

In either approach, beware object lifetime issues. In particular, if Game::idle does not call its callback before returning , you need to make sure that both the original object, and (in the second approach) the object returned by make_Runnable survive until the callback fires. This is why make_Runnable uses new .

Because I really don't like the answer that casts void* 's to other objects (almost never necessary in C++!) and nobody has posted an answer using the suggestions in the comments I'm going to suggest this.

Use a templated type for your callback!

Like this:

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

Then you don't lose all of your type safety (casting void* ) and it should be the fastest solution.


Also, where you are assigning a function pointer, you can change the code to be far more readable in this case:

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

Which is far nicer than having to write the correct function pointer type.

You cannot do that simply because pointer to [static] function is a single pointer sizeof void* . In contrary member function need more information, eg two pointers: one for this and another one for the function itself so member function pointer has sizeof > sizeof(void*) .

Therefore you have two options:

  1. to change signature of your idle() to this void idle(void (*)(), void*, int); so you will be able to pass this somehow.
  2. Or make static variable that will hold this pointer. But that assumes that only one death() can be at idle queue at any given moment of time.

1) is what people do usually in such cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM