Reputation: 930
I am learning c++ and would like to build something similar to C# events to handle interrupts in an embedded c++ project.
So far I came up with a solution that does almost what I want. However I need some help with polymorphism (?). The following code snippet is kind of a minimum example to reproduce my situation:
#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;
}
Expected console output:
Event
SpecialEvent
Event
SpecialEvent
Actual console output:
Event
SpecialEvent
Event
Event
What does the compiler/linker here that I am not aware of?
Upvotes: 3
Views: 141
Reputation: 9434
You have two methods in MyHandler. One of them overrides the base class method The other one does not.
One solution would be to declare both methods in the base class:
struct EventHandler
{
virtual void Esr (const Event& I) = 0;
virtual void Esr (const SpecialEvent& I) = 0;
};
That way the compiler can use the type of the argument to resolve the method at the EventHandler level.
If you wanted to avoid the requirement that all derived classes must overload both methods you could do something like this:
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));
}
};
To answer your question:
What does the compiler/linker here that I am not aware of?
In C++ a method call is resolved at compile/link time into either 1) a call to a particular block of code (the method body), or 2) an indirect call via a hidden data structure called a vtable. The actual vtable is determined at runtime, but the compiler has to decide which entry in the table to use for the call. (Google vtable for lots more information about what they are and how they are implemented.)
It has to base this resolution on what it's allowed to know. In this case based on the type of the pointer or reference through which the method is called. Note this is NOT necessarily the type of the actual object.
In your case when you call throgh handler
the compiler is allowed to know about both methods declared in MyHandler
so it can pick the one you expect, but when the call goes through sender
, it has to find a method declared in EventSender
. There's only one method declared in EventSender
. Fortunately the argument can be coerced into a const Event &
so the compiler is able to use that method. Thus it uses the vtable entry for that method. So it finds the vtable for MyHandler
[at runtime] and uses the vtable entry for
Esr (const Event& I)
and that's how you end up in the wrong method.
BTW: My answer is intended to explain what you are seeing and give you a way to fix your immediate problem. Jerry Coffin's answer gives you an alternative approach that should work better for you in the long term.
Upvotes: 2
Reputation: 490048
Here you're trying to use overloading, not classic (virtual function based) polymorphism.
What you want (at least as I understand it) is behavior that's essentially the same between using a handler
directly, and invoking it indirectly via a sender
. The variation that happens is between an Event
and a SpecialEvent
.
That being the case, classic polymorphism would involve a virtual function in Event
that's overridden in 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"; }
};
With this in place, a reference (or pointer) to an Event
will invoke the member for the actual type. Doing the polymorphism here means we only need one handler class, so the code ends up something like this:
#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"
}
Upvotes: 3
Reputation:
First of all, you cannot cast references to a descendant of a base class.
You'll need to use a pointer to that type, and using dynamic_cast
.
So, you have
EventSender sender (handler);
in main()
. The constructor of sender
binds to the base class of MyHandler
which is EventHandler
, since this is the parameter type in the constructor of MyHandler
(= EventHandler::EventHandler
). Therefore, EventHandler.Esr(const Event &)
is called, which happens to be virtual, so there is a pointer to MyHandler.Esr(const Event &)
.
Note that technically, Esr(const Event &)
and Esr(const SpecialEvent &)
are two different methods; they just happen to use the same name.
Upvotes: 1