0

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.

photon19
  • 9
  • 1
  • Look at the [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) how to do that. – πάντα ῥεῖ Dec 25 '18 at 20:33
  • I wrote quite clearly that CRTP would not suffice for the reasons provided in the post. Namely, I cannot have a pointer to a MessageSubscriber and a pointer to a MessageSubscriber coexist in the subscribers vector. – photon19 Dec 25 '18 at 20:33
  • The usual CRTP implementation uses a `static_cast(this)` an I cannot see why that can't be used in your implementation. Provide a [mcve] which shows the problems that would give you please. – πάντα ῥεῖ Dec 25 '18 at 20:37
  • Sure, but how does that address the main problem with having different types in the same vector? That problem will still exist under your proposed solution, no? Casting to Derived before putting into the subscribers vector would create different types in the vector which is not allowed. The other alternative would require some way of doing the cast inside OnMessage(), but since there is no way to know the derived types (there are many), then that also fails. – photon19 Dec 25 '18 at 20:38
  • You may still have a non-template common base class to store pointers in that vector. – πάντα ῥεῖ Dec 25 '18 at 20:48
  • How would that work with the the need to overload OnMessage() for the MessageSubscribers? Maybe I'm being an idiot but I don't see how that would work. Could you provide an example please? – photon19 Dec 25 '18 at 20:51
  • Still the same `static_cast(this)` idiom I mentioned above. – πάντα ῥεῖ Dec 25 '18 at 20:53
  • Where? How? When you suggested this earlier I wrote that of the two places where the cast would be performed, neither would be sufficient. – photon19 Dec 25 '18 at 20:58

1 Answers1

0

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.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thanks for your suggestion, Paul, but I am really trying to do everything with templates if possible. – photon19 Dec 25 '18 at 21:04
  • 1
    I don't see how you can, since some of the decisions you need to make are only known at runtime (if I understand the question correctly). – Paul Sanders Dec 25 '18 at 21:07
  • I am sufficiently stumped indeed. Do you have any insight as to why my attempt at specializing/overloading the member function templates does not work as intended? – photon19 Dec 25 '18 at 21:09
  • 1
    For the reason I just gave? Templates evaluate types known at compile time only. – Paul Sanders Dec 25 '18 at 21:14