plats1
plats1

Reputation: 930

C++ polymorphism: what am I missing?

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

Answers (3)

Dale Wilson
Dale Wilson

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

Jerry Coffin
Jerry Coffin

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

user1781434
user1781434

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

Related Questions