I have a base class which implements the following:
struct Consumer
{
template <typename T>
void callback(T msg) { /*null implementation */ }
};
I then have a class implement this:
struct Client : public Consumer
{
void callback(Msg1 msg);
void callback(Msg2 msg);
void callback(Msg3 msg);
};
The issue is I have a container of Client objects treated as Consumer* and I can't think of a way to get these Consumer objects to call the derived functions. My intended functionality is to have multiple Clients each of which implement an overloaded function for each Msg class that means something to them and the rest of the calls simply call the nu开发者_如何学Goll implementation in the base class
Any thoughts how I can get the derived class to be called? Right now I need to implement every overloaded function in Consumer and mark them as virtual.
Cheers, Graeme
If you really don't want to use virtual functions (this seems to be a perfect use case for them actually, but I don't know about your message classes), you can use the CRTP:
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);
};
The problem, of course, is that you cannot store Consumer
objects in a container any more. Since everything is compile time, the actual type of the client must be stored alongside the consumer object for the compiler to call the right callback function. Virtual functions allow you to wait until runtime for this...
Is there a reason not to have Msg
classes polymorphic and use standard virtual functions (other than "I have to rewrite all the code and I cannot") ?
EDIT If your concern is about message classes, why not use something like that, assuming message classes implement a DoSomething
member function: (this technique is known as 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);
}
};
You can customize the behavior of newImpl
to get what you want (eg. default actions if there is no DoSomething
member function in the message class, specialization for some message classes or anything else). This way, you implement Msg
classes like you would have done with your template solution, and you have a unique facade that you can pass to the virtual functions in your client classes.
If the Message classes are going to be very different, and client classes may react differently to them, and you are going to have a lot of message classes, this begins to smell. Or perhaps you have a candidate for the ugly and scary Visitor pattern.
Since you don't want to use virtual methods, the compiler would have to know statically (i.e. at compile time) which function to call. If you have different client objects in your container, there is now way the compiler could possibly know this. So I think there's no solution to your problem without using virtual methods (which are btw. exactly designed for this kind of situations...).
Of course you could alternatively using some switch
statements for manually deriving the concrete type, but this is neither elegant nor efficient (and you would have to hardcode all possible client types ...)
EDIT
Personally, I'd implement some base message class containing a type code and implement a switch
statement in the client class to handle different message types like:
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) { /* ... */ }
};
You could also make MsgBase
polymorphic (e.g. virtual destructor) and use typeid
to differentiate (more elegant but slightly less efficient ...)
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));
}
// ...
};
This is always a difficult situation to make totally extensible, as is the case usually with the Visitor pattern.
You end up needing up to V*T implementations where V is the number of "visitors" and T is the number of types being visited and will probably end up having to use a mixture of visitor and class factory pattern.
visitors here would be your consumers class factory would be used for the message types.
and your best way to make it totally extensible is to create new function "objects" for message/consumer pairs and a bit of double-dispatch to ensure the right one gets called.
In your case you have different messages come in and then you give them to your consumers who might handle them? So each message should have an identifiable "type" and your consumer should look up this type in a table to create a handler for it.
You can have one handler per type per consumer class.
精彩评论