c++多态性:我错过了什么?

C++ polymorphism: what am I missing?

本文关键字:什么 错过了 多态性 c++      更新时间:2023-10-16

我正在学习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

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

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

你想要的(至少在我的理解中)是直接使用handler和通过sender间接调用它之间本质上相同的行为。发生的变化是在EventSpecialEvent之间。

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

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

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

#include <iostream>
struct Event { 
    virtual void operator()() const { std::cout << "Eventn"; }
};
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 Eventn"; }
};
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"
}

在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获取更多关于它们是什么以及如何实现的信息)

它必须以它被允许知道的内容为基础。在这种情况下,基于调用方法所通过的指针或引用的类型。注意,这不一定是实际对象的类型。

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

的vtable条目
 Esr (const Event& I)

这就是你最终使用错误方法的原因。

顺便说一句:我的回答是为了解释你所看到的,并给你一个解决你眼前问题的方法。Jerry Coffin的答案为你提供了另一种方法,从长远来看,它应该对你更有效。

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

所以你有

EventSender sender (handler);

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

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

相关文章: