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

Я реализую вариант шаблона наблюдателя в 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).Следовательно, у вас не может быть некоторого шаблона CTRP по линиям MessageSubscriber<SafetyMessageSubscriberA>, MessageSubscriber<SafetyMessageSubscriberB>, поскольку аргументы шаблона должны быть одинаковыми, чтобы оба объекта были юридически разрешены в MessagePublisher::subscribers.

MyСамая последняя попытка решить эту проблему побудила меня попробовать некоторые варианты специализации шаблонов функций-членов, хотя и безуспешно.Я попробовал следующий вариант интерфейса шаблона:

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*.

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

1 Ответ

0 голосов
/ 25 декабря 2018

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...