我有一个实现以下内容的基类:
struct Consumer
{
template <typename T>
void callback(T msg) { /*null implementation */ }
};
然后我有一个类实现这个:
struct Client : public Consumer
{
void callback(Msg1 msg);
void callback(Msg2 msg);
void callback(Msg3 msg);
};
问题是我有一个Client对象的容器被视为Consumer *,我想不出让这些Consumer对象调用派生函数的方法。我的预期功能是拥有多个客户端,每个客户端为每个Msg类实现一个重载函数,这对他们来说意味着什么,其余的调用只是在基类中调用null实现
我有什么想法可以调用派生类吗?现在我需要在Consumer中实现每个重载函数,并将它们标记为虚拟。
干杯, 格雷姆
答案 0 :(得分:3)
如果你真的不想使用虚函数(实际上这似乎是一个完美的用例,但我不知道你的消息类),你可以使用CRTP:< / p>
template <typename U>
struct Consumer
{
template <typename T>
void callback(T msg)
{ static_cast<U*>(this)->callback(msg); }
};
struct Client : Consumer<Client>
{
void callback(Msg1 msg);
void callback(Msg2 msg);
void callback(Msg3 msg);
};
问题当然是您无法再将Consumer
个对象存储在容器中。由于所有内容都是编译时间,因此客户端的实际类型必须与使用者对象一起存储,以便编译器调用正确的回调函数。虚函数允许您等到运行时...
是否有理由不让Msg
类具有多态性并使用标准虚函数(除了“我必须重写所有代码而我不能”)?
编辑如果您关心的是消息类,为什么不使用类似的东西,假设消息类实现了DoSomething
成员函数:(这种技术称为 Type Erasure )
struct AnyMsg
{
template <typename Msg>
AnyMsg(Msg x) : impl(newImpl(x)) {}
void DoSomething() { impl->DoSomething(); }
private:
struct Impl
{
virtual ~Impl() {}
virtual void DoSomething() = 0;
};
// Probably better is std::unique_ptr if you have
// C++0x. Or `boost::scoped_ptr`, but you have to
// provide copy constructors yourself.
boost::shared_ptr<Impl> impl;
template <typename Msg>
Impl* newImpl(Msg m)
{
class C : public Impl
{
void DoSomething() { x.DoSomething(); }
Msg x;
public:
C(Msg x) : x(x) {}
};
return new C(m);
}
};
您可以自定义newImpl
的行为以获得您想要的内容(例如,如果消息类中没有DoSomething
成员函数,则为默认操作,某些消息类的专门化或其他任何内容)。这样,您可以像使用模板解决方案一样实现Msg
类,并且您可以将唯一的外观传递给客户端类中的虚拟函数。
如果Message类会有很大的不同,并且客户端类可能对它们做出不同的反应,并且您将要有很多消息类,这就开始闻起来了。或许你有一个丑陋和可怕的候选人Visitor pattern。
答案 1 :(得分:1)
由于您不想使用虚方法,编译器必须静态地(即在编译时)知道要调用哪个函数。如果容器中有不同的客户端对象,那么编译器现在可能知道这一点。所以我认为没有使用虚拟方法(这是完全针对这种情况设计的......),你的问题没有解决方案......。
当然,您也可以使用一些switch
语句手动派生具体类型,但这既不优雅也不高效(您必须对所有可能的客户类型进行硬编码......)
修改强>
就个人而言,我将实现一些包含类型代码的基类消息类,并在客户端类中实现switch
语句来处理不同的消息类型,如:
struct MsgBase {
int type;
};
struct Consumer {
virtual void callback(MsgBase msg) { };
};
struct Client : public Consumer {
void callback(MsgBase msg) {
switch (msg.type) {
case MSGTYPE1:
callback((Msg1)msg);
break;
case MSGTYPE2:
callback((Msg2)msg);
break;
// ...
}
}
void callback(Msg1 msg) { /* ... */ }
void callback(Msg2 msg) { /* ... */ }
};
您还可以使MsgBase
多态(例如虚拟析构函数)并使用typeid
来区分(更优雅但效率稍低......)
struct Client : public Consumer {
void callback(MsgBase* msg) {
if (typeid(*msg) == typeof(Msg1))
callback(static_cast<Msg1*>(msg));
else if (typeid(*msg) == typeof(Msg2))
callback(static_cast<Msg2*>(msg));
}
// ...
};
答案 2 :(得分:1)
这通常是一个难以完全扩展的情况,通常是访客模式的情况。
您最终需要V * T实现,其中V是“访问者”的数量,T是访问类型的数量,可能最终不得不使用访问者和类工厂模式的混合。
这里的访客将成为您的消费者 类工厂将用于消息类型。
并且使其完全可扩展的最佳方法是为消息/消费者对创建新的函数“对象”,并进行一些双重调度以确保调用正确的对象。
在您的情况下,您有不同的消息,然后您将它们提供给可能处理它们的消费者?因此,每条消息都应该具有可识别的“类型”和您的消费者 应该在表中查找此类型以为其创建处理程序。
每个消费者类每种类型可以有一个处理程序。