I am implementing a variation of the observer pattern in C++. However, because of the nature of the nature of my project, it CANNOT USE ANY VIRTUAL MEMBER FUNCTIONS, as the aggregate overhead from vtable lookups and cache misses is unacceptable.

Were I to create interfaces via virtual member functions, I would trivially write the following:

template <class MessageType>
class MessageSubscriber {
public:
  virtual void OnMessage(MessageType *message) = 0;
};

template <class MessageType>
class MessagePublisher {
public:
  void AddSubscriber(MessageSubscriber<MessageType> *subscriber) {
    subscribers.push_back(subscriber);
  } 

protected:
  void Publish(MessageType *message) {
    for (auto subscriber : subscribers)
      subscriber.OnMessage(message);
  }

private:
  std::vector<MessageSubscriber<MessageType>*> subscribers;
};

Then, for example, I could have classes that implement MessageSubscriber for some MessageType, SafetyMessage, like so:

class SafetyMessageSubscriberA : public MessageSubscriber<SafetyMessage> {
public:
  virtual void OnMessage(SafetyMessage *message) override {
    /* process message */
  }
};

class SafetyMessageSubscriberB : public MessageSubscriber<SafetyMessage> {
public:
  virtual void OnMessage(SafetyMessage *message) override {
    /* process message */
  }
};

class SafetyMessagePublisher : public MessagePublisher<SafetyMessage> {
public:
  void Run {
    /* manipulate message data */
    this->Publish(&message);
  }
private:
  SafetyMessage message;
};

This would get the job done, but, as emphasized earlier, the vtable lookup overhead is unacceptable in the context of the application despite the polymorphic convenience that it provides and is also needed for the application. Naturally, then, I tried several approaches centering around the static polymorphism that can be leveraged through templates.

I first tried to utilize CTRP, but it fails in this case because the pointers contained in MessagePublisher::subscribers must point to the same base class when MessagePublisher::Publish(MessageType *message) is called. Ergo, you could not have some CTRP pattern along the lines of MessageSubscriber<SafetyMessageSubscriberA>, MessageSubscriber<SafetyMessageSubscriberB>, as the template arguments would need to be the same for both objects to legally be allowed in MessagePublisher::subscribers.

My most recent attempt at the problem has lead me to try some variations of member function template specialization, albeit unsuccessfully. I have tried the following variation on the pattern interface:

class MessageSubscriber {
public:
  template <class MessageType>
  void OnMessage(MessageType *message);
};

class MessagePublisher {
public:
  template <class MessageType>
  void Publish(MessageType *message) {
    for (auto subscriber: subscribers)
      subscriber->OnMessage<MessageType>(message);   
  }

private:
  std::vector<MessageSubscriber*> subscribers;
};

template<class MessageType> 
void MessageSubscriber::OnMessageOnMessage(MessageType *message) {
  /* "interface" call; do nothing */
}

With implementations such as:

class SafetyMessageSubscriberA : public MessageSubscriber {
public:
  // declare for legal overload
  template <class MessageType>
  void OnMessage(MessageType *message);
};

class SafetyMessageSubscriberB : public MessageSubscriber {
public:  
  // declare for legal overload
  template <class MessageType>
  void OnMessage(MessageType *message);  
};

template<>
void SafetyMessageSubscriberA::OnMessage<SafetyMessage*>OnMessage(SafetyMessage *message) {
  /* process message */
}

template<> 
void SafetyMessageSubscriberB::OnMessage<SafetyMessage*>OnMessage(SafetyMessage *message) {
  /* process message */
}

When I tried this, however, MessagePublisher::Publish(SafetyMessage *message) would always call the generic MessageSubscriber::OnMessage(MessageType *m)implementation for the base class, not the ones that were implemented for the derived classes specific to SafetyMessage*.

Am I incorrectly specializing the function templates as intended, or is there another more efficient solution? I apologize in advance for any imprecise wording as it relates to the concepts of overloading and member template specialization.

1

There are 1 best solutions below

4
On

You can cut out one level of indirection by using C-style function pointers in place of virtual functions. Thus, in the declaration of your base class you might have something like:

void (*) OnMessage (BaseClass *self, MessageType *message);

You then initialise this instance variable in each of your derived classes' constructors to point to the appropriate static member function, which in turn allows you to call it via a single indirect call (as opposed to two if you went via the vtable).

Finally, sadly, you will need to cast self in each of the target functions in the derived classes, which is the price you pay for all this trickery. Either that or cast the function signature when assigning the function pointer. I will post a fuller example if interested - let me know.