Я реализую вариант шаблона наблюдателя в C ++. Однако из-за характера моего проекта НЕ МОЖЕТ ИСПОЛЬЗОВАТЬ ЛЮБЫЕ ВИРТУАЛЬНЫЕ ФУНКЦИИ ЧЛЕНА, поскольку совокупные издержки из-за поиска в vtable и кеша недопустимы.
Если бы я создавал интерфейсы через виртуальные функции-члены, я бы тривиально написал следующее:
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;
};
Тогда, например, я мог бы иметь классы, которые реализуют MessageSubscriber
для некоторых MessageType
, SafetyMessage
, вот так:
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;
};
Это позволит выполнить работу, но, как подчеркивалось ранее, издержки поиска vtable недопустимы в контексте приложения, несмотря на полиморфное удобство, которое оно обеспечивает, и оно также необходимо для приложения. Естественно, тогда я попробовал несколько подходов, сосредоточенных вокруг статического полиморфизма, который можно использовать с помощью шаблонов.
Сначала я попытался использовать CTRP, но в этом случае он не работает, потому что указатели, содержащиеся в MessagePublisher::subscribers
должен указывать на тот же базовый класс, когда MessagePublisher::Publish(MessageType *message)
называется. Ergo, вы не могли бы иметь некоторый шаблон CTRP в соответствии с MessageSubscriber<SafetyMessageSubscriberA>
, MessageSubscriber<SafetyMessageSubscriberB>
, поскольку аргументы шаблона должны быть одинаковыми для обоих объектов, которые должны быть разрешены в MessagePublisher::subscribers
,
Моя последняя попытка решить эту проблему побудила меня попробовать некоторые варианты специализации шаблонов функций-членов, хотя и безуспешно. Я попробовал следующий вариант интерфейса шаблона:
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 */
}
С реализациями, такими как:
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 */
}
Когда я попробовал это, однако, MessagePublisher::Publish(SafetyMessage *message)
всегда будет называть общий MessageSubscriber::OnMessage(MessageType *m)
реализация для базового класса, а не те, которые были реализованы для производных классов, специфичных для SafetyMessage*
,
Я неправильно специализирую шаблоны функций, как предполагалось, или есть другое более эффективное решение? Заранее прошу прощения за неточную формулировку, поскольку она связана с концепциями перегрузки и специализации шаблонов элементов.
Вы можете вырезать один уровень косвенности, используя указатели функций в стиле C вместо виртуальных функций. Таким образом, в объявлении вашего базового класса у вас может быть что-то вроде:
void (*) OnMessage (BaseClass *self, MessageType *message);
Затем вы инициализируете эту переменную экземпляра в каждом из конструкторов ваших производных классов, чтобы указывать на соответствующую статическую функцию-член, которая, в свою очередь, позволяет вам вызывать ее с помощью одного косвенного вызова (в отличие от двух, если вы прошли через vtable).
Наконец, к сожалению, вам нужно будет бросить self
в каждой из целевых функций в производных классах — это цена, которую вы платите за весь этот обман. Либо так, либо приведение сигнатуры функции при назначении указателя функции. Я опубликую более полный пример, если интересно — дайте мне знать.
Других решений пока нет …