将成员函数指针传递给父 class 产生编译器错误

Pass member function pointer to parent class yields compiler error

我想让子 classes 注册回调到他们的父 class 以便父 class 的用户可以调用具有已知函数签名的子的方法.

typedef int(*Func)(int);

class A
{
  public:
    void registerFunc(Func f)
    {}
};

class B : public A
{

  public:
    B()
    {
      A::registerFunc(&B::myF);
    }
    int myF(int x) {
       // do stuff with member variables
       return 3; 
    }
};

但是我得到这个编译器错误

main.cpp:18:23: error: cannot initialize a parameter of type 'Func' (aka 'int (*)(int)') with an rvalue of type 'int (B::*)(int)'
      A::registerFunc(&B::myF);
                      ^~~~~~~
main.cpp:8:28: note: passing argument to parameter 'f' here
    void registerFunc(Func f)

这是一个用简明示例说明错误的 Repl。

https://replit.com/@Carpetfizz/RudeSmoothComments#main.cpp

相关线程中接受的答案建议覆盖 A 中声明的虚函数,但我的用例实际上需要动态回调注册。

你可以试试这个。

typedef std::function<int (int)> Func;

class A
{
  public:
    void registerFunc(Func f)
    {}
};

class B : public A
{

  public:
    B()
    {
      A::registerFunc(std::bind(&B::myF, *this, std::placeholders::_1));
    }
    int myF(int x) {
       // do stuff with member variables
       return 3;
    }
};

如果我理解目标(并且相信我,那是一个粗略的 'if'),您想要指定某些 A 派生的某些成员以从某些 A 成员调用作为派遣 'callback' 机械师。如果是这样,那么在评论中回答你的问题,是的,一个函数和绑定可以做到这一点。在 sfinae 的帮助下甚至可以 semi-protected:

例子

#include <iostream>
#include <type_traits>
#include <functional>
#include <memory>

struct A
{
    virtual ~A() = default;

    std::function<void(int)> callback = [](int){};

    template<class Derived>
    std::enable_if_t<std::is_base_of<A, Derived>::value>
    registerCallback(void (Derived::*pfn)(int))
    {
        using namespace std::placeholders;
        callback = std::bind(pfn, dynamic_cast<Derived*>(this), _1);
    }

    void fire(int arg)
    {
        callback(arg);
    }
};

struct B : public A
{
    void memberfn(int arg)
    {
        std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
    }
};

struct Foo
{
    void memberfn(int arg)
    {
        std::cout << __PRETTY_FUNCTION__ << ':' << arg << '\n';
    }
};

int main()
{
    std::unique_ptr<A> ptr = std::make_unique<B>();
    ptr->registerCallback(&B::memberfn);
    // ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
    ptr->fire(42);
}

输出

void B::memberfn(int):42

零件

第一部分很简单。我们将一个成员变量 callback 声明为一个 std::function<void(int)> 实例。这是我们最终将绑定可调用对象点的地方。默认值是不执行任何操作的 lambda。


第二部分……有点复杂:

template<class Derived>
std::enable_if_t<std::is_base_of<A, Derived>::value>
registerCallback(void (Derived::*pfn)(int))

这将registerCallback声明为一个可用的成员函数,它接受一个non-static成员函数指针以一个int作为参数,但是只有 如果承载该成员函数的 class 或其中的派生函数是 A(或 A 本身)的派生。某些具有成员 void foo(int) 的非 A 派生 Foo 不会 编译。


接下来,回调本身的设置。

using namespace std::placeholders;
callback = std::bind(pfn, dymamic_cast<Derived*>(this), _1);

这只是将 pointer-to-member 绑定到 this dynamic-cast 到派生类型(这有更好的工作,否则我们有麻烦了,请参阅此谩骂结尾处的最后警告),并设置 call-time 占位符。您看到的 _1 来自 std::placeholders 命名空间,用于延迟向回调提供参数,直到我们实际调用它(需要它的地方,您会看到之后)。有关详细信息,请参阅 std::placehholders


最后,fire 成员执行此操作:

void fire(int arg)
{
    callback(arg);
}

这将使用提供的参数调用已注册的函数对象。成员函数 this 都已连接到对象中。参数arg用于填充我们前面提到的占位符


这个测试驱动程序很简单:

int main()
{
    std::unique_ptr<A> ptr = std::make_unique<B>();
    ptr->registerCallback(&B::memberfn);
    // ptr->registerCallback(&Foo::memberfn); // WILL NOT WORK
    ptr->fire(42);
}

这将创建一个新的 B,将其托管在一个动态 A 指针中(因此您知道没有有趣的事情在进行)。尽管如此,因为 B 派生自 A,所以 registerCallback sfinae 过滤通过了检查并且回调成功注册。然后我们调用 fire 方法,传递我们的 int 参数 42,它将被发送到回调等


警告:能力越大,责任越大

即使那些有防止传递非A派生成员函数的保护,也绝对none来自转换本身。制作一个基本的 A,传递一个 B 成员(这将起作用,因为 A 是它的基础)是微不足道的,但实际上没有 B

您可以在运行时通过 dynamic_cast 捕获它,我们目前 不是 错误检查。例如:

registerCallback(void (Derived::*pfn)(int))
{
    using namespace std::placeholders;
    Derived *p = dynamic_cast<Derived*>(this);
    if (p)
        callback = std::bind(pfn, p, _1);
}

你可以选择比较冒险的路。就个人而言,我会检测 null 情况并抛出异常以确保安全(呃)