C ++多态:我缺少什么?

时间:2016-09-16 16:31:14

标签: c++ templates polymorphism

我正在学习c ++,并希望构建类似于C#事件的东西来处理嵌入式c ++项目中的中断。

到目前为止,我想出了一个几乎能满足我想要的解决方案。但是我需要一些关于多态性的帮助(?)。以下代码片段是重现我的情况的最小示例:

#include <iostream>       

struct Event
  { };

struct EventHandler
  {
    virtual void Esr (const Event& I) { }
  };

struct EventSender
  {
    EventSender (EventHandler& Handler) : _Handler (Handler) { }

    template <typename T>
    void SendEvent (const T&) const
      {
        _Handler.Esr (T ());
      }

    EventHandler& _Handler;
  };

struct SpecialEvent : public Event
  { };

struct MyHandler : public EventHandler
  {
    void Esr (const Event& I) override { std::cout << "Event" << std::endl; }
    void Esr (const SpecialEvent& I) { std::cout << "SpecialEvent" << std::endl; }
  };            

int main()
  {
    MyHandler handler;
    EventSender sender (handler);

    /* Invoke directly  */
    handler.Esr (Event ());
    handler.Esr (SpecialEvent ());

    /* Invoke indirectly  */
    sender.SendEvent (Event ());
    sender.SendEvent (SpecialEvent ());  // Expected cout msg: "SpecialEvent"

    return 0;
  }

预期的控制台输出:

Event
SpecialEvent
Event
SpecialEvent

实际控制台输出:

Event
SpecialEvent
Event
Event

这里的编译器/链接器我不知道什么?

3 个答案:

答案 0 :(得分:3)

在这里,您尝试使用重载,而不是经典(基于虚函数)的多态。

你想要的东西(至少在我理解的情况下)是直接使用handler和通过sender间接调用它之间的行为基本相同。发生的变化介于EventSpecialEvent之间。

在这种情况下,经典多态将涉及Event中被SpecialEvent覆盖的虚拟函数:

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; }
};

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; }
};

有了这个,Event的引用(或指针)将调用实际类型的成员。在这里执行多态意味着我们只需要一个处理程序类,因此代码最终会是这样的:

#include <iostream>

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; }
};

struct EventHandler {
    void Esr(const Event& I) const { I(); }
};

struct EventSender {
    template <typename T>
    void SendEvent (const T& t) const {
        handler.Esr(t);
    }

    EventHandler handler;
};

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; }
};

int main() {
    EventHandler handler;
    EventSender sender;

    /* Invoke directly  */
    handler.Esr (Event ());
    handler.Esr (SpecialEvent ());

    /* Invoke indirectly  */
    sender.SendEvent (Event ());
    sender.SendEvent (SpecialEvent ());  // Expected cout msg: "SpecialEvent"
}

答案 1 :(得分:2)

MyHandler中有两种方法。其中一个覆盖基类方法 另一个没有。

一种解决方案是在基类中声明两种方法:

struct EventHandler
{
    virtual void Esr (const Event& I) = 0;
    virtual void Esr (const SpecialEvent& I) = 0;
};

这样编译器就可以使用参数类型来解析EventHandler级别的方法。

如果你想避免要求所有派生类必须重载两种方法,你可以这样做:

 struct EventHandler
 {
    virtual void Esr (const Event& I) = 0;
    virtual void Esr (const SpecialEvent& I)
    {
        // if not overridden, use the non-specialized event handler.
        Esr(reinterpret_cast<const Event &>(I));
    }
 };

回答你的问题:

  

这里的编译器/链接器我不知道什么?

在C ++中,方法调用在编译/链接时解析为1)对特定代码块(方法体)的调用,或2)通过称为vtable的隐藏数据结构的间接调用。实际的vtable是在运行时确定的,但编译器必须决定表中的哪个条目用于调用。 (Google vtable提供了有关它们是什么以及它们如何实现的更多信息。)

它必须将此决议基于它允许知道的内容。在这种情况下,基于通过其调用方法的指针或引用的类型。请注意,这不一定是实际对象的类型。

在您调用throgh handler的情况下,允许编译器知道MyHandler中声明的两种方法,以便它可以选择您期望的方法,但是当调用通过{{1}时},它必须找到sender中声明的方法。 EventSender中只声明了一种方法。幸运的是,参数可以强制转换为EventSender,因此编译器可以使用该方法。因此它使用该方法的vtable条目。因此它在const Event & [在运行时]找到vtable并使用vtable条目

MyHandler

那是你如何以错误的方法结束。

顺便说一句:我的回答是为了解释你所看到的内容并给你一个解决眼前问题的方法。 Jerry Coffin的回答为您提供了一种替代方法,从长远来看,这种方法应该更适合您。

答案 2 :(得分:1)

首先,您不能转换对基类后代的引用。 您需要使用指向该类型的指针,并使用dynamic_cast

所以,你有

EventSender sender (handler);
main()中的

sender的构造函数绑定到MyHandler的基类EventHandler,因为这是MyHandler(= EventHandler::EventHandler)构造函数中的参数类型。因此,EventHandler.Esr(const Event &)被调用,恰好是虚拟的,因此有一个指向MyHandler.Esr(const Event &)的指针。

请注意,技术上Esr(const Event &)Esr(const SpecialEvent &)是两种不同的方法;他们碰巧使用相同的名字。