使用派生类成员函数指针的多态性?

8
我看到类似的问题之前在SO上被问过,但我看到的那些似乎不完全涉及我的使用情况。具体来说,我想知道我的编译失败是错误的结果,还是我试图做的被禁止的结果。
背景
我希望实现委托模式,用于处理事件。我认为,对于我的需求,最好的方法可能是一个成员函数指针的映射,由std::string(表示事件类型)索引。
我开始尝试使用std::function来实现这一点,但遇到了一些问题,然后决定仅使用原始MFPs重试。(我仍然愿意考虑使用std::function,并且我将接受一个向下面展示如何使用该方法实现我的确切需求的答案。但我仍然想知道我的当前方法有什么问题。)
我能够让这个方法在单个类中工作。但是我实际上希望委托映射由抽象基类提供,然后派生类将其委托注册到该映射中。除非我在下面的代码中犯了一些错误,否则这似乎是不可能的;似乎成员函数指针不能是多态的。
我得到的编译错误如下:
mfp5.cpp: In constructor ‘Derived::Derived()’:
mfp5.cpp:41:21: error: cannot convert ‘int (Derived::*)(const Base::EventContext&)’ to ‘std::map<std::basic_string<char>, int (Base::*)(const Base::EventContext&)>::mapped_type {aka int (Base::*)(const Base::EventContext&)}’ in assignment
_delegates["foo"] = &Derived::FooEventHandler;

问题

  1. 下面的代码中我犯了错误吗?还是这真的不允许?简而言之,我有一个Base::*的std :: map,并且我想将一些Derived::*插入其中。
  2. 是否有其他推荐的方法来实现这个目标?

代码

class Base
{
  public:
    struct EventContext
    {
      int data1;
    };
    Base() {}
    virtual int ProcessEvent(std::string event, EventContext ctx) =0;

  protected:
    typedef int (Base::* EventHandler)(const EventContext& context);
    typedef std::map<std::string, EventHandler> EventDelegateMap;
    EventDelegateMap _delegates;
};

.

class Derived: Base
{
  public:
    Derived();
    int ProcessEvent(std::string event, EventContext ctx);

  private:
    int FooEventHandler(const EventContext& context);
    int BarEventHandler(const EventContext& context);
    int QuxEventHandler(const EventContext& context);
};

Derived::Derived() :Base()
{
  _delegates["foo"] = &Derived::FooEventHandler;  // error
  _delegates["bar"] = &Derived::BarEventHandler;  // error
  _delegates["qux"] = &Derived::QuxEventHandler;  // error
}

我很好奇你使用 std::function 的解决方案是什么样子的。使用 std::function 可以使整个系统更加通用(如果需要,可以使用 lambda 表达式或非成员函数,或者想出一个不需要继承的系统)。 - Some programmer dude
基本上,我尝试了这个(以及其他几种排列组合):typedef int (Base::*EventHandler)(const EventContext& context); typedef std::function EventDelegate; typedef std::map EventDelegateMap;但是我被一些晦涩难懂的编译器错误所困扰。(C++模板的可读性最差的部分就是编译器警告和错误。) - Cognitive Hazard
此外,对于我的使用情况,我不需要灵活使用lambda等。 - Cognitive Hazard
具有讽刺意味的是,我接受的答案主张使用lambda来解决这个问题。 :) - Cognitive Hazard
3个回答

4

看起来你想使用std::function, 我会这样说:

class Base
{
  public:
    struct EventContext
    {
      int data1;
    };
    Base() {}
    virtual int ProcessEvent(std::string event, EventContext ctx) =0;

  protected:
    typedef std::function<int(const EventContext&)> HandlerType;
    typedef std::map<std::string, HandlerType> EventDelegateMap;
    EventDelegateMap _delegates;
};

class Derived: Base
{
  public:
    Derived();
    int ProcessEvent(std::string event, EventContext ctx){ return 0; }

  private:
    int FooEventHandler(const EventContext& context){ return 0; }
    int BarEventHandler(const EventContext& context){ return 0; }
    int QuxEventHandler(const EventContext& context){ return 0; }
};

Derived::Derived() :Base()
{
    auto self = this; // Some gcc versions cannot capture this correctly.
    _delegates["foo"] = [=](const EventContext& context) { return self->FooEventHandler(context); };
    _delegates["bar"] = [=](const EventContext& context) { return self->BarEventHandler(context); };
    _delegates["qux"] = [=](const EventContext& context) { return self->QuxEventHandler(context); };
}

我应该工作...
编辑:如@Joachim在评论中提到的那样,您也可以使用std :: bind()来生成所需的std :: function对象,例如。
_delegates["foo"] = std::bind(&Derived::FooEventHandler, this, std::placeholders::_1);

我使用lambda表达式来展示实际上可以在lambda中实现整个逻辑。这种方法的主要优势是,如果你正在实现更多的处理程序,那么付出的努力会更少,而我总是支持更少的努力... :)


1
为什么不使用std::bind来处理函数?例如 _delegates["foo"] = std::bind(&Derived::FooEventHandler, this, std::placeholders::_1); - Some programmer dude
1
@JoachimPileborg,也可以,我的意图是要表明您实际上不需要成员函数,在lambda中,您可以实现整个逻辑... - Nim
我还没有深入研究C++ lambda表达式。在我的ProcessEvent()方法在map中查找到lambda委托后,应该如何调用它?(我的仅基于MFP的代码使用return (this->*(iter->second))(ctx) - Cognitive Hazard
回答自己的问题:显然我需要将其更改为iter->second(ctx)。我接受这个答案(并向@JoachimPileborg致以荣誉提及)。 - Cognitive Hazard

3

我认为这是一种类型错误,因为如果允许赋值,你可以做如下操作:

/* This step is iffy... */
void (Base::* basePtr)() = &Derived::someMethod;

/* ... because of this. */
Base b;
(b.*basePtr)(); // Ooops...

在这里,我们在Base对象b内部调用由basePtr指向的函数的最后一行。但这是个问题,因为接收者对象应该属于Derived类型!

希望这有所帮助!


所以基本上,这种方法只适用于作为Base一部分的MFPs? - Cognitive Hazard

2

对于多态函数的输入参数,例如传递给成员函数指针的this指针,只能是逆变的。由于this也是输出参数,而输出参数只能是协变的,因此this必须是不变的,但这部分与您的问题无关。这些语句直接来源于Liskov替换原则

基本上,您无法执行您正在尝试进行的赋值,因为编译器无法证明您派生类型的成员函数总是在派生对象上调用。


我非常欣赏这个回答的学术角度。谢谢。 - Cognitive Hazard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接