【发布时间】: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;
};
然后,例如,我可以为某些MessageType、SafetyMessage 提供实现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<SafetyMessageSubscriberA>、MessageSubscriber<SafetyMessageSubscriberB> 的 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<Derived*>(this)我不明白为什么不能在您的实现中使用它。请提供minimal reproducible example,其中显示了您可能遇到的问题。 -
当然,但这如何解决在同一个向量中具有不同类型的主要问题?在您提出的解决方案下,这个问题仍然存在,不是吗?在放入订阅者向量之前强制转换为 Derived 会在向量中创建不同的类型,这是不允许的。另一种选择需要某种方式在 OnMessage() 中进行强制转换,但由于无法知道派生类型(有很多),那么这也会失败。
-
您可能仍然有一个非模板公共基类来在该向量中存储指针。
标签: c++ templates virtual-functions static-polymorphism static-dispatch