【问题标题】:Polymorphism with derived member function pointers?派生成员函数指针的多态性?
【发布时间】:2013-12-09 19:12:59
【问题描述】:

(我看到以前曾在 SO 上提出过类似的问题,但我所看到的问题似乎并没有完全触及我的用例。特别是,我想知道我的编译失败是错误的结果,或者是我尝试禁止的结果。)

背景

我希望实现委托模式,用于事件处理。我认为可能满足我需求的最佳方法是成员函数指针的映射,由 std::string (代表事件类型)索引。

我开始尝试使用 std::function 来完成此任务,但遇到了一些问题,然后决定仅使用原始 MFP 进行尝试。 (我仍然愿意考虑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 或非成员函数,或者提出一个不需要继承的系统)。
  • 基本上,我尝试了这个(以及其他一些排列):typedef int (Base::*EventHandler)(const EventContext&amp; context); typedef std::function&lt;EventHandler&gt; EventDelegate; typedef std::map&lt;std::string, EventDelegate&gt; EventDelegateMap; 但我被一些神秘的、无法形容的编译器错误所阻碍。 (编译器警告和错误的可理解性是 C++ 模板中最糟糕的部分。)
  • 另外,对于我这里的用例,我不需要灵活地使用 lambdas 等。
  • 具有讽刺意味的是,我接受的答案提倡为此使用 lambda。 :)

标签: c++ inheritance polymorphism member-function-pointers


【解决方案1】:

看来你想使用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 中实现整个逻辑。这种方法的主要优点是,如果您要实现更多的处理程序,它的工作量会更少,我总是赞成更少的工作量...... :)

【讨论】:

  • 为什么不使用std::bind 的功能?喜欢_delegates["foo"] = std::bind(&amp;Derived::FooEventHandler, this, std::placeholders::_1);
  • @JoachimPileborg,也可以,我的意图是表明您并不需要成员函数,使用 lambda,您可以在 lambda 中实现整个逻辑...
  • 我还没有深入研究 C++ lambda。我的 ProcessEvent() 方法在 map 中 find() 之后应该如何调用该 lambda 委托? (我的 Base-only MFP 代码使用 return (this-&gt;*(iter-&gt;second))(ctx)
  • 回答我自己的问题:显然我需要将其更改为 iter-&gt;second(ctx)。我接受这个答案(荣誉提及@JoachimPileborg)
【解决方案2】:

我认为这是一个类型错误,因为如果允许分配,您可以执行以下操作:

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

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

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

希望这会有所帮助!

【讨论】:

  • 所以基本上,这种方法仅限于本身就是 Base 一部分的 MFP?
【解决方案3】:

多态函数的输入参数,例如,传递给成员函数指针的this指针,只能是逆变的。由于this 也是一个输出参数并且输出参数只能是协变的,所以this 必须是不变的,但该部分与您的问题无关。这些语句直接来自Liskov Substitution Principle

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

【讨论】:

  • 我真的很欣赏这个回复的学术角度。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-27
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多