On the Difficulties of Event Handlers in Object Oriented Languages

Maintained by: David Austin

1.0 Introduction
When developing a modular, large scale system such as the software needed to control a robot, one often needs to respond to events. Events can come from a variety of sources, for example the receipt of a message from another module is a type of (very frequent) event but also events come from external sources such as the user pressing the emergency stop. While the lower levels of control of a robot are performed at a fixed frequency, there is still a need to respond to events at this low level and the higher levels of control are fundamentally based on the low frequency occurence of events.

In an object-oriented language, the problem can be stated as follows: What methodology should be used to notify objects of the occurence of events? This document discusses methods for doing this and some of the non-obvious shortcomings of some of the methods that the author has experienced.

2.0 Member Function Event Notification
The obvious way to notify an object of an event is to call a member function. For example, one could do the following:
class NotifyObjectBase {
public:
  NotifyObjectBase();

  virtual void HandleEvent(EventType event) = 0;
};


class Object1 : public NotifyObjectBase {
public:
  Object1();

  void HandleEvent(EventType event) {
    if (event.type == 0)
      cout << "Event 0 happened\n";
  }
};

Ok, this looks great and seems to exemplify the virtues of object oriented programming. Whenever a class needs to be notified of an event, we simply call the HandleEvent member function. All classes that need to detect events can be derived from the NotifyObjectBase base class.

However, all is not fine. Introduction of a new class illustates the problems:
class NotifyObjectBase {
public:
  NotifyObjectBase();

  virtual void HandleEvent(EventType event) = 0;
};


class Object1 : public NotifyObjectBase {
public:
  Object1();

  void HandleEvent(EventType event) {
    if (event.type == 0)
      cout << "Event 0 happened\n";
  }
};

class Object2 : public Object1 {
public:
  Object2();

  void HandleEvent(EventType event) {
    if (event.type == 1)
      cout << "Event 1 happened\n";
  }
};

The programmers of Object1 and Object2 would probably expect that their handler would be called for all events. Instead, the implementation of Object2 has in some sense changed the implementation of Object1. Normally, we wish to change the definition of a member function when we overload it, but not in this case. Here overloading produces non-obvious results and/or introduces nasty dependencies between objects.

One way to work around this problem is to call parent member functions.

2.1 Calling Parent Member Functions

To avoid the problem of overloading of member functions completely removing the previous behaviour, one can try calling the parent function if the event is not handled at the current level. For example:
class NotifyObjectBase {
public:
  NotifyObjectBase();

  virtual int HandleEvent(EventType event) {
    return 0;
  };
};


class Object1 : public NotifyObjectBase {
public:
  Object1();

  int HandleEvent(EventType event) {
    if (event.type == 0) {
      cout << "Event 0 happened\n";
      return 1;
    }

    return NotifyObjectBase::HandleEvent(event);
  }
};

class Object2 : public Object1 {
public:
  Object2();

  int HandleEvent(EventType event) {
    if (event.type == 1) {
      cout << "Event 1 happened\n";
      return 1;
    }

    return Object1::HandleEvent(event);
  }
};

While this methodology for event handling works, it has become a bit messy with a extra dependency on the parents object(s). Also, the case of multiple inheritance gets really messy. For example:
class NotifyObjectBase {
public:
  NotifyObjectBase();

  virtual int HandleEvent(EventType event) {
    return 0;
  };
};


class Object1 : public NotifyObjectBase {
public:
  Object1();

  int HandleEvent(EventType event) {
    if (event.type == 0) {
      cout << "Event 0 happened\n";
      return 1;
    }

    return NotifyObjectBase::HandleEvent(event);
  }
};

class Object2 : public Object1 {
public:
  Object2();

  int HandleEvent(EventType event) {
    if (event.type == 1) {
      cout << "Event 1 happened\n";
      return 1;
    }

    return Object1::HandleEvent(event);
  }
};

class Object3 : public Object1 {
public:
  Object3();

  int HandleEvent(EventType event) {
    if (event.type == 2) {
      cout << "Event 2 happened\n";
      return 1;
    }

    return Object1::HandleEvent(event);
  }
};

class Object4 : public Object2, Object3 {
public:
  Object4();

  int HandleEvent(EventType event) {
    if (event.type == 3) {
      cout << "Event 3 happened\n";
      return 1;
    }

    return Object2::HandleEvent(event) || Object3::HandleEvent(event);
  }
};

Now the event handler for Object1 can be called twice for the same event! One could add flags to fix this but the whole structure is starting to become an uncontrollable mess and certainly has none of the initial attraction of the first example shown above.

3.0 Static Function Event Notification
An alternative to member function notification is to use static functions. In general, it is not possible to obtain pointers to member functions and to call these later (for example, C++ does not permit this). However, for static functions (or non-member functions), it is possible to obtain a pointer and to call the function using the pointer. The down-side of this approach is that in both a static function and a non-member function, one cannot use the this pointer to the object or the member variables and functions of the object. However, it is possible to access the public variables and member functions through a pointer or reference to the object and, in a static member function, one can access the private variables and member functions as well.

Need a code example here.

4.0 Conclusion
The first conclusion is that it's not as easy as it seems! My decision has been to adopt the static function approach even though this will probably cause object-oriented programming purists considerable nausea. My justification is that the static function approach is the most general and can simply be used to implement the member function approach as follows:
class NotifyObjectBase {
public:
  NotifyObjectBase();

  virtual int HandleEvent(EventType event) {
    return 1;
  }

  static int EventHandlerFunction(EventType event, NotifyObjectBase *that) {
    return that->HandleEvent(event);
  }
};

Copyright 2001 David Austin