Clean ways to identify derived class types.

J

JC

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
 
J

Jonathan Lee

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
 
A

AnonMail2005

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
 
V

Victor Bazarov

JC said:
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
 
B

Bart van Ingen Schenau

JC said:
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().

Thanks!
J

Bart v Ingen Schenau
 
A

AnonMail2005

/// 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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,001
Messages
2,570,254
Members
46,850
Latest member
VMRKlaus8

Latest Threads

Top