如何将成员函数指针传递给采用常规函数指针的函数?

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

我有一个播放器 class 看起来像这样(精简到这个问题需要什么):

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

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

kill()death()reset() 函数如下所示:

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

    Game::idle(dPtr, 48);
}

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

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

idle函数是Game的一个静态成员函数,它接受一个函数指针和一个整数n,在n tick后调用该函数。这是函数,实现应该无关紧要:

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

这段代码给我错误:

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]

所以我将行从

    void (*dPtr)() = &death;

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

解决这个问题。但是后来我对空闲函数的调用是不正确的,因为它需要一个常规函数指针,而我传入了一个成员函数指针,因此给了我错误:

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

所以我的问题是: 如何将成员函数指针 Player::*dPtr 传递给以 void (*)() 作为参数的空闲函数?

或者有其他方法可以解决我之前的错误,该错误禁止我使用不合格成员函数的地址来形成指向成员函数的指针吗?

要通过指针调用成员函数,需要两个个指针:一个指向函数本身的指针,一个指向对象的指针this。您的 Game::idle API 不支持这种用法。您需要对其进行更改,以便它至少将一个参数(通常为 void * 类型)传递给回调。然后你可以使用下面的模式:

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

您必须为要调用的每个实例方法 X 编写一个静态 call_X 方法。


另一种方法,可以说更符合 C++ 的习惯和灵活,涉及较少显式写出的代码,但具有更高的运行时成本(三个间接函数调用和每次调用的堆免分配循环,而不是单个间接函数调用)是让 Game::idle 使用虚拟回调方法获取特定 class 的对象。然后给 class 一个模板 subclass,它可以调用任何实现 operator() 的东西,例如 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);
}

你不能让 Game::idle 直接获取 std::bind 的结果,因为那个对象的类型是未指定的(并且取决于你如何调用 std::bind),所以它只能被使用作为 template 函数调用的参数。对适配器 class 的虚拟方法调用是保持 Game::idle 编译外联并仍然让它使用绑定调用对象的唯一方法。

无论采用哪种方法,都要注意对象的生命周期问题。特别是,如果 Game::idle 在返回 之前没有调用其回调 ,则需要 确保 both 原始对象,并且(在第二种方法中) make_Runnable 返回的对象在回调触发之前一直存在。这就是 make_Runnable 使用 new.

的原因

你不能这样做,因为指向 [static] 函数的指针是一个大小为 void* 的指针。相反,成员函数需要更多信息,例如两个指针:一个用于 this,另一个用于函数本身,因此成员函数指针具有 sizeof > sizeof(void*).

因此你有两个选择:

  1. 将您的 idle() 签名更改为此 void idle(void (*)(), void*, int); 以便您能够以某种方式通过 this
  2. 或者创建静态变量来保存 this 指针。但这假设在任何给定时刻只有一个 death() 可以处于空闲队列。

1) 是人们在这种情况下通常会做的事情。

因为我真的不喜欢将 void* 转换为其他对象的答案(在 C++ 中几乎从来没有必要!)并且没有人使用评论中的建议发布答案我要去提出这个建议。

为回调使用模板化类型!

像这样:

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

这样你就不会失去所有类型安全性(转换 void*),它应该是最快的解决方案。


此外,在分配函数指针的地方,您可以更改代码以在这种情况下更具可读性:

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

这比必须编写正确的函数指针类型要好得多。

另一个答案提到你需要两个指针。然而,C++ 已经带有用于执行此操作的容器,因此使用它们会使您的代码更简单。 (在 C++03 中,下面的一些 std:: 项是 std::tr1::)。

示例代码:

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

终生注意事项:std::bind 按值绑定。使用 *this 意味着创建 Player 的副本并将其存储在 std::function 对象中,必要时随它一起复制。

使用this意味着函数对象存储了一个指针,所以如果你实际将函数对象存储在Game::idle中,你必须注意在删除它之前不要破坏这个Player Game::idle 列表中的函数对象。