A C++ Implementation of Publisher-Subscriber

Pattern catalog entries don't usually include implementations, because they are meant to be language independent. It is the programmer's job to provide the implementation. Some patterns are so useful, they are provided in well known libraries. For example, publisher and subscriber classes are provided in the Java utility package, where they are called Observable and Observer. Specializations of publisher and subscriber classes are also provided in the Microsoft Foundation Class library, where they are called CDocument and CView, and IBM's Visual Age library, where they are called Notifier and Subscriber. Unfortunately, the standard C++ library doesn't include Publisher and Subscriber, so we'll have to implement them ourselves. There are many approaches, we follow one loosely based on the Java implementation.

pubsub.h

We place the publisher and subscriber class declarations in a header file called pubsub.h. Here is the structure of the file:

/*
 * File:            pop\util\pubsub.h
 * Programmer:      Pearce
 * Copyright (c):   2000, all rights reserved.
 */
#ifndef PUBSUB_H
#define PUBSUB_H
#include <list>  // for our subscriber list
using namespace std;
class Publisher; // forward reference

class Subscriber { ... };
class Publisher { ... };

#endif

In addition to a do-nothing virtual destructor, the subscriber interface contains a pure virtual update function. To allow for the possibility that a subscriber might subscribe to multiple publishers, we pass a pointer to the notifying publisher. To allow messages to be sent from the publisher, we provide an optional generic "message" pointer:

class Subscriber
{
public:
  virtual ~Subscriber() {}
  virtual void update(Publisher* who, void* what = 0) = 0;
};

A publisher maintains a list of pointers to subscribers. Monitors implementing the subscriber interface can add themselves to the list using subscribe(), and remove themselves using unsubscribe(). Devices can call the update() function of each registered monitor by calling notify(). A client may need to change the state of a device without notifying the monitors. This can be done by first setting the notifyEnabled flag to false.

class Publisher
{
public:
   Publisher() { notifyEnabled = true; }
   virtual ~Publisher() {}
   void subscribe(Subscriber* s) { subscribers.push_back(s); }
   void unsubscribe(Subscriber* s) { subscribers.remove(s); }
   void notify(void* what = 0, Subscriber *s = 0);
   void setNotifyEnabled(bool flag) { notifyEnabled = flag; }
   bool getNotifyEnabled() const { return notifyEnabled; }
private:
   list<Subscriber*> subscribers;
   bool notifyEnabled;
};

pubsub.cpp

Including the implementation of notify() in the Publisher class declaration would make it inline, but some compilers won't allow inline functions containing iterative statements. Therefore, we move the implementation to a file called pubsub.cpp. We must not forget to include pubsub.h in this file:

/*
 * File:            pop\util\pubsub.cpp
 * Programmer:      Pearce
 * Copyright (c):   2000, all rights reserved.
 */
#include "pubsub.h"

Here is our implementation of notify(). Notice that notifyEnabled is automatically set to true at the end of the function. This protects against clients who may forget to set the flag back to true after setting it to false. The notify function has two parameters. The first parameter is an optional, generic message. The second parameter points to a subscriber that is not to be notified. This feature is useful when a subscriber wants to send a message to its fellow subscribers through the publisher. In this case the sender calls the notify function passing a message and its address. Every subscriber receives the message except the sender. Recall that both parameters have the null pointer as a default argument.

void Publisher::notify(void* what, Subscriber* s)
{
   if (notifyEnabled)
   {
      list<Subscriber*>::iterator p;
      for(p = subscribers.begin(); p != subscribers.end(); p++)
         if (*p != s) (*p)->update(this, what);
   }
   notifyEnabled = true;
}