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.