Clean ways to identify derived class types.

Discussion in 'C++' started by JC, Jul 2, 2009.

  1. JC

    JC Guest

    I'm designing an application that uses a simple event-based model for
    passing messages between objects. It's a basic "observer" setup. I
    have it set up something like this (I'm just typing these here,
    leaving a lot out I know):


    class Event {
    };

    class EventListener {
    public:
    virtual void onEvent (const Event &);
    };

    class EventSource {
    public:
    void addListener (EventListener *);
    protected:
    void notifyListeners (const Event &);
    };


    Specific events (possibly with extra event-specific info) are derived
    from Event. Note that an EventListener receives all types of events
    that an EventSource generates -- it's all-or-nothing rather than
    registering for specific types of events. This is important to me and
    simplifies a lot of the logic throughout the application.

    So, here is my question. In the various implementations of
    EventListener::eek:nEvent, some of the EventListeners handle a lot of
    different event types, and the implementations end up looking rather
    ugly, sort of like (again just typed here, pardon any errors):


    void SomeEventListener::eek:nEvent (const Event &e) {

    const AnEvent *a = dynamic_cast<const AnEvent *>(&e);
    if (a) {
    handleAnEvent(a);
    return;
    }

    const OtherEvent *b = dynamic_cast<const OtherEvent *>(&e);
    if (b) {
    handleOtherEvent(b);
    return;
    }

    // and so on...

    }


    Are there other good ways to do this? I'm pretty much asking just out
    of curiosity, as the above method actually does work adequately, even
    though it's sort of painful to look at. Fortunately, in this
    particular application, performance penalties of dynamic_cast are
    negligible and not an issue, but what if performance did matter --
    would there still be a way to keep the flexibility of EventListeners
    not having to register for specific event types?

    One obvious solution is to have type ID numbers, unique to each event
    type, with a virtual int getType() or some such. However, I don't
    think that's really a good solution here -- I think it will be a
    maintenance problem in the future if new events are added, to ensure
    uniqueness of the IDs (unless IDs are noted in a document somewhere,
    which I guess is OK, or if they're all declared in some common header
    or even assigned dynamically on first access, which works but breaks
    encapsulation a bit). I could use ID strings with the same name as the
    class to ensure uniqueness, but *if* the goal was performance, I'm not
    sure if I'd be comfortable with string compares every time.

    Thanks!
    J
    JC, Jul 2, 2009
    #1
    1. Advertising

  2. JC

    Jonathan Lee Guest

    > Are there other good ways to do this?

    As an alternative, you could use <typeinfo> and typeid, then maybe a
    map to translate the class name to a result suitable for a switch().

    IMHO, since event types are usually well known at the time of design
    (with the possibility of a generic "user" event), just provide the
    event type. I personally use an enum declared public within the class
    so that I don't have to worry about numbering them myself, and I
    ensure uniqueness.

    OR, your base Event class could have a virtual function handle(), and
    you could move handleAnEvent() and handleOtherEvent() to the derived
    class. Then you would just have to call a->handle(), and b->handle().
    I don't think that's the design you want, though.

    >but *if* the goal was performance


    Since event types can run into the dozens I'm not sure that
    dynamic_cast()-ing down the list of all of them is good for
    performance. But maybe you weren't implying that.

    --Jonathan
    Jonathan Lee, Jul 2, 2009
    #2
    1. Advertising

  3. JC

    Guest

    On Jul 2, 4:51 am, JC <> wrote:
    > I'm designing an application that uses a simple event-based model for
    > passing messages between objects. It's a basic "observer" setup. I
    > have it set up something like this (I'm just typing these here,
    > leaving a lot out I know):
    >
    > class Event {
    >
    > };
    >
    > class EventListener {
    > public:
    >   virtual void onEvent (const Event &);
    >
    > };
    >
    > class EventSource {
    > public:
    >   void addListener (EventListener *);
    > protected:
    >   void notifyListeners (const Event &);
    >
    > };
    >
    > Specific events (possibly with extra event-specific info) are derived
    > from Event. Note that an EventListener receives all types of events
    > that an EventSource generates -- it's all-or-nothing rather than
    > registering for specific types of events. This is important to me and
    > simplifies a lot of the logic throughout the application.
    >
    > So, here is my question. In the various implementations of
    > EventListener::eek:nEvent, some of the EventListeners handle a lot of
    > different event types, and the implementations end up looking rather
    > ugly, sort of like (again just typed here, pardon any errors):
    >
    > void SomeEventListener::eek:nEvent (const Event &e) {
    >
    >   const AnEvent *a = dynamic_cast<const AnEvent *>(&e);
    >   if (a) {
    >     handleAnEvent(a);
    >     return;
    >   }
    >
    >   const OtherEvent *b = dynamic_cast<const OtherEvent *>(&e);
    >   if (b) {
    >     handleOtherEvent(b);
    >     return;
    >   }
    >
    >   // and so on...
    >
    > }
    >
    > Are there other good ways to do this? I'm pretty much asking just out
    > of curiosity, as the above method actually does work adequately, even
    > though it's sort of painful to look at. Fortunately, in this
    > particular application, performance penalties of dynamic_cast are
    > negligible and not an issue, but what if performance did matter --
    > would there still be a way to keep the flexibility of EventListeners
    > not having to register for specific event types?
    >
    > One obvious solution is to have type ID numbers, unique to each event
    > type, with a virtual int getType() or some such. However, I don't
    > think that's really a good solution here -- I think it will be a
    > maintenance problem in the future if new events are added, to ensure
    > uniqueness of the IDs (unless IDs are noted in a document somewhere,
    > which I guess is OK, or if they're all declared in some common header
    > or even assigned dynamically on first access, which works but breaks
    > encapsulation a bit). I could use ID strings with the same name as the
    > class to ensure uniqueness, but *if* the goal was performance, I'm not
    > sure if I'd be comfortable with string compares every time.
    >
    > Thanks!
    > J


    Look up the Visitor design pattern. This is exactly what you are
    looking for.
    It is somewhat involved to explain and understand but once you
    understand it, it is a very useful tool for certain situations.

    HTH
    , Jul 2, 2009
    #3
  4. JC wrote:
    > I'm designing an application that uses a simple event-based model for
    > passing messages between objects. It's a basic "observer" setup. I
    > have it set up something like this (I'm just typing these here,
    > leaving a lot out I know):
    >
    >
    > class Event {
    > };
    >
    > class EventListener {
    > public:
    > virtual void onEvent (const Event &);
    > };
    >
    > class EventSource {
    > public:
    > void addListener (EventListener *);
    > protected:
    > void notifyListeners (const Event &);
    > };
    >
    >
    > Specific events (possibly with extra event-specific info) are derived
    > from Event. Note that an EventListener receives all types of events
    > that an EventSource generates -- it's all-or-nothing rather than
    > registering for specific types of events. This is important to me and
    > simplifies a lot of the logic throughout the application.
    >
    > So, here is my question. In the various implementations of
    > EventListener::eek:nEvent, some of the EventListeners handle a lot of
    > different event types, and the implementations end up looking rather
    > ugly, sort of like (again just typed here, pardon any errors):
    >
    >
    > void SomeEventListener::eek:nEvent (const Event &e) {
    >
    > const AnEvent *a = dynamic_cast<const AnEvent *>(&e);
    > if (a) {
    > handleAnEvent(a);
    > return;
    > }
    >
    > const OtherEvent *b = dynamic_cast<const OtherEvent *>(&e);
    > if (b) {
    > handleOtherEvent(b);
    > return;
    > }
    >
    > // and so on...
    >
    > }
    >
    >
    > Are there other good ways to do this? I'm pretty much asking just out
    > of curiosity, as the above method actually does work adequately, even
    > though it's sort of painful to look at. Fortunately, in this
    > particular application, performance penalties of dynamic_cast are
    > negligible and not an issue, but what if performance did matter --
    > would there still be a way to keep the flexibility of EventListeners
    > not having to register for specific event types?
    >
    > One obvious solution is to have type ID numbers, unique to each event
    > type, with a virtual int getType() or some such. However, I don't
    > think that's really a good solution here -- I think it will be a
    > maintenance problem in the future if new events are added, to ensure
    > uniqueness of the IDs (unless IDs are noted in a document somewhere,
    > which I guess is OK, or if they're all declared in some common header
    > or even assigned dynamically on first access, which works but breaks
    > encapsulation a bit). I could use ID strings with the same name as the
    > class to ensure uniqueness, but *if* the goal was performance, I'm not
    > sure if I'd be comfortable with string compares every time.


    If you decide to go with the IDs, then your Event classes could obtain
    the IDs from some kind of registration system. There is no need to
    assign them statically. This solution is akin to run-time class
    identification existing in MFC (although that one does use strings, I
    believe) and in some proprietary applications.

    Of course, if you suddenly get the need to serialize those IDs, then you
    can't have them [potentially] change every time your app runs...

    V
    --
    Please remove capital 'A's when replying by e-mail
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Jul 3, 2009
    #4
  5. JC wrote:

    > I'm designing an application that uses a simple event-based model for
    > passing messages between objects. It's a basic "observer" setup. I
    > have it set up something like this (I'm just typing these here,
    > leaving a lot out I know):
    >
    >
    > class Event {
    > };
    >
    > class EventListener {
    > public:
    > virtual void onEvent (const Event &);
    > };
    >
    > class EventSource {
    > public:
    > void addListener (EventListener *);
    > protected:
    > void notifyListeners (const Event &);
    > };
    >
    >
    > Specific events (possibly with extra event-specific info) are derived
    > from Event. Note that an EventListener receives all types of events
    > that an EventSource generates -- it's all-or-nothing rather than
    > registering for specific types of events. This is important to me and
    > simplifies a lot of the logic throughout the application.
    >
    > So, here is my question. In the various implementations of
    > EventListener::eek:nEvent, some of the EventListeners handle a lot of
    > different event types, and the implementations end up looking rather
    > ugly, sort of like (again just typed here, pardon any errors):
    >
    >
    > void SomeEventListener::eek:nEvent (const Event &e) {
    >
    > const AnEvent *a = dynamic_cast<const AnEvent *>(&e);
    > if (a) {
    > handleAnEvent(a);
    > return;
    > }
    >
    > const OtherEvent *b = dynamic_cast<const OtherEvent *>(&e);
    > if (b) {
    > handleOtherEvent(b);
    > return;
    > }
    >
    > // and so on...
    >
    > }
    >
    >
    > Are there other good ways to do this?


    My first thought was to use the Visitor design pattern, in combination
    with your observer:

    /// In file EventListener.h
    /* forward declaration of all event types */
    class Event;
    class AnEvent;
    class OtherEvent;
    class UnrelatedEvent;

    /* declaration of base EventListener */
    class EventListener {
    public:
    void onEvent (const Event &);
    virtual void handleEvent(const Event &) = 0;
    virtual void handleEvent(const AnEvent &);
    virtual void handleEvent(const OtherEvent &);
    virtual void handleEvent(const UnrelatedEvent &);
    };

    /// In file Event.h
    #include "EventListener.h"
    /* declaration of base Event */
    class Event {
    public:
    virtual void callEventhandler(EventListener *listener) const
    { /* call the listener back with the specific event type */
    listener->handleEvent(*this);
    }
    };

    /// In file AnEvent.h
    #include "EventListener.h"
    #include "Event.h"
    /* declaration of specific event class AnEvent. Other specific events
    are similar */
    class AnEvent : public Event {
    public:
    virtual void callEventhandler(EventListener *listener) const
    { /* call the listener back with the specific event type */
    listener->handleEvent(*this);
    }
    };

    /// In file EventSource.h
    #include "Event.h"
    #include "EventListener.h"

    /* class EventSource is unchanged */
    class EventSource {
    public:
    void addListener (EventListener *);
    protected:
    void notifyListeners (const Event &);
    };


    /// In file EventListener.cpp
    #include "EventListener.h"
    /* include headers for all event classes */
    #include "Event.h"
    #include "AnEvent.h"
    #include "OtherEvent.h"
    #include "UnrelatedEvent.h"

    /* Implementation of EventListener methods */
    void EventListener::eek:nEvent (const Event &event)
    { /* Ask the event to call us back */
    event.callEventHandler(this);
    }

    void handleEvent(const AnEvent &event)
    { /* Default behaviour: fall back to base-class handler */
    handleEvent(static_cast<const Event&>(event));
    }

    void handleEvent(const OtherEvent &event)
    { /* Default behaviour: fall back to base-class handler */
    handleEvent(static_cast<const Event&>(event));
    }

    void handleEvent(const UnrelatedEvent &event)
    { /* Default behaviour: fall back to base-class handler */
    handleEvent(static_cast<const Event&>(event));
    }


    A specific EventListener class would override the handleEvent functions
    for the events that the class is interested in (plus the
    handleEvent(const Event&) overload as I made that one mandatory).

    Speed-wise, the (sequence of) dynamic_cast<> operations is replaced by
    one additional virtual call (and the assumption that there is no
    multiple inheritance among the Event classes).
    The big drawback of this method is that the base EventListener class has
    to know about all the possible Event classes in order to provide all the
    required overloads of handleEvent().

    <snip>
    > Thanks!
    > J


    Bart v Ingen Schenau
    --
    a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
    c.l.c FAQ: http://c-faq.com/
    c.l.c++ FAQ: http://www.parashift.com/c -faq-lite/
    Bart van Ingen Schenau, Jul 3, 2009
    #5
  6. JC

    Guest

    > /// In file EventListener.h
    > /* forward declaration of all event types */
    > class Event;
    > class AnEvent;
    > class OtherEvent;
    > class UnrelatedEvent;
    >
    > /* declaration of base EventListener */
    > class EventListener {
    > public:
    >   void onEvent (const Event &);
    >   virtual void handleEvent(const Event &) = 0;
    >   virtual void handleEvent(const AnEvent &);
    >   virtual void handleEvent(const OtherEvent &);
    >   virtual void handleEvent(const UnrelatedEvent &);
    >
    > };
    >


    Two points here:

    1. For the situation that I've encountered, I don't think you want a
    function that handles the base class - the whole point of this class
    is to handle each of the specific *derived* classes. Also, in the
    situations that I've encountered, this isn't even possible because the
    base class is typically an abstract base class.

    2. Also, it's easier to write the specific handlers derived from
    EventListener if the EventListener class has default implementations
    that do nothing. This way, each derived class only has to override
    what it actually handles and not worry about the other events that it
    doesn't handle.

    HTH
    , Jul 5, 2009
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. clintonG
    Replies:
    12
    Views:
    694
    David Jessee
    May 1, 2004
  2. Replies:
    1
    Views:
    378
    myork
    May 23, 2007
  3. Replies:
    1
    Views:
    369
    Victor Bazarov
    May 23, 2007
  4. Replies:
    8
    Views:
    494
  5. Jorgen Grahn
    Replies:
    33
    Views:
    1,274
    James Kanze
    Jun 26, 2009
Loading...

Share This Page