Passing function type information instead of C ++ template virtual function

I have a base class that implements the following:

struct Consumer { template <typename T> void callback(T msg) { /*null implementation */ } }; 

Then I implement this class:

 struct Client : public Consumer { void callback(Msg1 msg); void callback(Msg2 msg); void callback(Msg3 msg); }; 

The problem is that I have a container of client objects processed as "Consumer" *, and I cannot think of a way to force these consumer objects to call derived functions. My supposed functionality is to have multiple Clients, each of which implements an overloaded function for each Msg class, which means something for them, and the rest of the calls just call a zero implementation in the base class

Any thoughts on how I can get a derived class to call? Right now I need to implement every overloaded function in Consumer and mark it as virtual.

Cheers, Graeme

+4
source share
3 answers

If you really do not want to use virtual functions (for them this is really an ideal use case, but I do not know about your message classes), you can use 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 can no longer store Consumer objects in a container. Since all compilation time, the actual client type must be stored next to the consumer object so that the compiler can call the correct callback function. Virtual functions allow you to wait until that time ...

Is there a reason not to have Msg polymorphic classes and use standard virtual functions (except "I have to rewrite all the code, but I can not")?

EDIT If your problem is with message classes, why not use something similar, assuming message classes implement the DoSomething member function: (this method 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 (for example, default actions if the message class does not have a DoSomething member class function, specialization for some message classes, or something else). Thus, you implement the Msg classes, as it would with your solution for templates, and you have a unique facade that you can pass to virtual functions in your client classes.

If the message classes will be very different, and the client classes may respond differently to them, and you will have many message classes, it starts to smell. Or perhaps you have a candidate for an ugly and scary visitor template .

+3
source

Since you do not want to use virtual methods, the compiler must know statically (i.e. at compile time) which function to call. If you have different client objects in your container, it is now possible that the compiler might know this. Therefore, I think that there is no solution to your problem without using virtual methods (which, incidentally, are designed for such situations ...).

Of course, you could use several switch to manually output a specific type, but this is neither elegant nor efficient (and you will have to hard-code all possible client types ...)

EDIT

Personally, I would use some base message class containing type code and implement a switch in the client class to handle various types of messages, such as:

 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 can also make MsgBase polymorphic (like a virtual destructor) and use typeid to differentiate (more elegant, but a little 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)); } // ... }; 
+1
source

This is always a difficult situation to make it fully extensible, as is usually the case with the visitor template.

You need up to V * T implementations, where V is the number of "visitors" and T is the number of types visited and you will probably have to use a mixture of the visitor template and the factory class in the end.

visitors here will be your consumers class factory will be used for message types.

and your best way to make it fully extensible is to create a new "objects" function for message / consumer pairs and a bit of double sending to ensure the right to be called.

In your case, different messages appear, and then you give them to your consumers, who can process them? Therefore, each message should have an identifiable "type" and your consumer should look for this type in the table to create a handler for it.

Each handler can have one handler for each class.

+1
source

Source: https://habr.com/ru/post/1338065/


All Articles