【问题标题】:C++ Static Polymorphism––Referencing Specialized Template Methods Overloaded In Derived Class From Base Class PointerC++ 静态多态性——从基类指针引用派生类中重载的专用模板方法
【发布时间】:2018-12-25 20:31:38
【问题描述】:

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

然后,例如,我可以为某些MessageTypeSafetyMessage 提供实现MessageSubscriber 的类,如下所示:

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::Publish(MessageType *message) 时,MessagePublisher::subscribers 中包含的指针必须指向同一个基类。因此,您不能有一些类似于MessageSubscriber&lt;SafetyMessageSubscriberA&gt;MessageSubscriber&lt;SafetyMessageSubscriberB&gt; 的 CTRP 模式,因为模板参数需要相同,这两个对象才能合法地在 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)implementation,而不是为特定于SafetyMessage* 的派生类实现的那些。

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

【问题讨论】:

  • 看看CRTP是怎么做的。
  • 我写得很清楚,由于帖子中提供的原因,CRTP 是不够的。也就是说,我不能在订阅者向量中同时存在指向 MessageSubscriber 的指针和指向 MessageSubscriber 的指针。
  • 通常的 CRTP 实现使用 static_cast&lt;Derived*&gt;(this) 我不明白为什么不能在您的实现中使用它。请提供minimal reproducible example,其中显示了您可能遇到的问题。
  • 当然,但这如何解决在同一个向量中具有不同类型的主要问题?在您提出的解决方案下,这个问题仍然存在,不是吗?在放入订阅者向量之前强制转换为 Derived 会在向量中创建不同的类型,这是不允许的。另一种选择需要某种方式在 OnMessage() 中进行强制转换,但由于无法知道派生类型(有很多),那么这也会失败。
  • 您可能仍然有一个非模板公共基类来在该向量中存储指针。

标签: c++ templates virtual-functions static-polymorphism static-dispatch


【解决方案1】:

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

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

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

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

【讨论】:

  • 谢谢你的建议,Paul,但如果可能的话,我真的很想用模板来做所有事情。
  • 我不知道你怎么做,因为你需要做出的一些决定只有在运行时才知道(如果我正确理解了这个问题)。
  • 我确实被难住了。您对为什么我尝试专门化/重载成员函数模板没有按预期工作有任何见解吗?
  • 因为我刚才给出的原因?模板仅评估编译时已知的类型。
猜你喜欢
  • 1970-01-01
  • 2012-05-24
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 2014-07-31
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多