Статический полиморфизм C ++ — ссылка на специализированные методы шаблона, перегруженные в производном классе из указателя базового класса

Я реализую вариант шаблона наблюдателя в 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*,

Я неправильно специализирую шаблоны функций, как предполагалось, или есть другое более эффективное решение? Заранее прошу прощения за неточную формулировку, поскольку она связана с концепциями перегрузки и специализации шаблонов элементов.

0

Решение

Вы можете вырезать один уровень косвенности, используя указатели функций в стиле C вместо виртуальных функций. Таким образом, в объявлении вашего базового класса у вас может быть что-то вроде:

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

Затем вы инициализируете эту переменную экземпляра в каждом из конструкторов ваших производных классов, чтобы указывать на соответствующую статическую функцию-член, которая, в свою очередь, позволяет вам вызывать ее с помощью одного косвенного вызова (в отличие от двух, если вы прошли через vtable).

Наконец, к сожалению, вам нужно будет бросить self в каждой из целевых функций в производных классах — это цена, которую вы платите за весь этот обман. Либо так, либо приведение сигнатуры функции при назначении указателя функции. Я опубликую более полный пример, если интересно — дайте мне знать.

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]