c++多态性:我错过了什么?
C++ polymorphism: what am I missing?
我正在学习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
间接调用它之间本质上相同的行为。发生的变化是在Event
和SpecialEvent
之间。
在这种情况下,经典的多态性将涉及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[在运行时]并使用
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 &)
是两个不同的方法;他们只是碰巧使用了相同的名字。
- 我的C++合并排序代码不起作用。我在这里错过了什么?
- 我是否为邪恶刽子手的构造函数错过了什么?
- C++:将向量传递给函数,然后在main中调用函数.错过了什么
- 随机访问迭代器:我错过了什么?
- 在这个while循环中我错过了什么?
- 我是否错过了什么,或者虚拟呼叫的性能并不像人们所说的那样糟糕
- 我在变量上收到 3 个 C4703 错误,我认为我已经正确初始化了,但我不确定我错过了什么
- 如果这不是 boost::lockfree::d etail::freelist 中的错误,我在这里错过了什么
- 尝试使用类,但未打印任何内容.不会生成任何错误.我错过了什么吗?
- 尝试实现通过引用传递的向量以与二叉树一起使用,我错过了什么
- 此代码的输出是什么?我在这里错过了什么吗?
- 下面显示的代码片段在 Coliru 和 Ideone 中编译,但根据 iso § 8.5 p6,它不应该,还是我错过了什么?
- AFAIK,下面的代码不应该编译,但它可以在 clang 和 GCC 中编译。我在这里错过了什么?
- 仅在标头中定义时内联的函数.我错过了什么吗?
- 我错过了什么吗?我一直在输出"No file found!"
- 安全布尔成语 - 我是否找到了更简单的方法,还是我错过了什么
- 我错过了什么
- 无法完成Rice(Ruby)的简单示例.我错过了什么
- 为什么包括警卫对我没有影响?我是不是错过了什么
- 据我所知,下面的函数不是constexpr,而是用clang和g++编译的代码.我错过了什么