0

我正在用 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

我最近对该问题的尝试使我尝试了成员函数模板专业化的一些变体,尽管没有成功。我在模式界面上尝试了以下变体:

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*

我是否按预期错误地专门化了功能模板,还是有另一种更有效的解决方案?对于与重载和成员模板专业化的概念有关的任何不精确的措辞,我提前道歉。

4

1 回答 1

0

您可以通过使用 C 风格的函数指针代替虚函数来减少一级间接。因此,在基类的声明中,您可能会有类似的内容:

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

然后,您在每个派生类的构造函数中初始化此实例变量以指向适当的静态成员函数,这反过来又允许您通过单个间接调用来调用它(如果您通过 vtable 进行调用,则相对于两个)。

最后,可悲的是,您将需要self在派生类中强制转换每个目标函数,这是您为所有这些诡计付出的代价。在分配函数指针时,或者强制转换函数签名。如果有兴趣,我会发布一个更完整的示例 - 让我知道。

于 2018-12-25T20:52:41.423 回答