In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-04-09 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the knowledge of "how to implement C++ delegation and message feedback template". Many people will encounter this dilemma in the operation of actual cases. then let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Inheritance + polymorphism
At first glance, it seems like a natural choice. The class in the library sets the response handler to a virtual function, and the client program can inherit this class and overload the response function. Take a socket class as an example, you can provide an OnRecv function to respond to the arrival of network packets. The client program only needs to overload the OnRecv and do its own processing.
Struct Socket {/ / base class virtual void OnRecv ();}; stuct MySocket {/ / your event-handle class virtual void OnRecv () {/ * do sth here... * /}}
Question: most of the time, it's really annoying to do this, especially when doing Mini Program, or when you need to make a rapid prototype, it's annoying to see that a small program inherits a lot of things. I just want to make it easier to bind message responses as quickly as those scripting languages, instead of starting with inheritance-- I'm already afraid of seeing a long class inheritance tree, and most of the time it's not necessary to inherit the whole class at all. or some classes provide only one interface instead of concrete classes or require multiple inheritance. The most troublesome thing is that sometimes you need to change the response processing, is it possible to inherit several? so many virtual tables are also a waste.
Comments: in order to use Socket, you must inherit Socket, which can be said to be a problem with the design of Socket. If you need to implement similar functionality, it can be written as follows, although it is not fundamentally different from inheriting Socket, but it does separate the message handling class from the implementation of Socket.
Struct SocketEventHandler {virtual void OnRecv () {/ *... * /} virtual void OnSend () {/ *... * /}}; struct Socket {void set_handler (SocketEventHandler* h) {handler_ = h;} private: SocketEventHandler* handler_;}; struct MyHandler: SocketEventHandler {void OnRecv () {...}; Socket s; MyHandler h; s.set_handler (& h)
Put aside inheritance, do we have a simple and clear way to express it? I can't help thinking of the callback function of the c era.
Callback function (CallBack)
It's very simple, it's just a function pointer. The OnRecv just now can be written like this
Struct Socket {void OnRecv () {if (OnRecvHandlewritten null) OnRecvHandle ();} void (* OnRecvHandle) ();}
The client program only needs to write a MyOnRecv function and assign it to OnRecvHandle.
Void MyOnRecv (); / / your event-handle function Socket foo; foo.OnRecvHandle = MyOnRecv
Question: it's very simple, you don't need to inherit a class to handle it, and you can replace different handlers at any time. In fact, the essence of polymorphism is also function pointers, but polymorphism is the unified management of function pointers with vtable. Callback functions should pay special attention to whether the function pointer is empty, so * wraps a judgment process outside. The problem of callback functions * is that the type is not safe.
Delegate (Delegation)
What is the commission? The essence of delegation is to provide a type-safe dynamic message response transfer mechanism.
In the past, I knew nothing about delegation, I thought it was nothing more than a type-safe smart pointer, and the so-called Multi-Cast Delegation is nothing more than a smart pointer count ancestor, is there an Any-Cast Delegation? I don't know, maybe there is, it's nothing more than intelligent pointer count + random number generator.
However, it is actually not that simple. You can encapsulate the function pointer I just mentioned and wrap it in a class, but this directly causes the response of a message to be only a fixed function pointer type, or even a lovely Functor or a member function of a class. You might argue with me about how this is possible, isn't it possible to do it in template? Let's look at an example.
Suppose a delegate class Dummy_Delegation has a member function to connect to the handler function template < class T > void Dummy_Delegation::Connect (T _ F); yes, _ F may not necessarily be a function pointer, or it can be Functor, and we use _ F () to call the response function, and everything looks good-- but, unfortunately, this _ F cannot be saved for call when a message is generated.
All because of this damn template < class T >, you can't define a T-type variable or pointer in Dummy_Delegation to hold _ F. To say the least, if you use T as a template for the entire Dummy, you can't avoid fixing the type when the template is instantiated. As a result, the versatility of the whole Delegation is greatly reduced.
In fact, we want to have a Delegation that can dynamically bind message responses to member functions of any class as long as the function types are the same. Note that we are talking about any class here. This requires us to shield the coupling relationship between the signal generator and the response class, that is, they do not know who the other is or even the type information of the other.
Is this method feasible? Yes!
Bridge delegation (Bridge Delegation)-using generics + polymorphism to implement
Allow me to invent a noun: bridge delegation (Bridge Delegation)
It's really interesting to implement such a thing, in fact, many systems like gtk+/qt that require "signal / feedback" (signal/slot) are implemented this way.
When it comes to GP and Template, it can really be said that a hundred schools of thought contend, just as boost and loki are still fighting for the position of the new C++ standard smart pointer. And Functor is the basis of a lot of GP algo, such as sort/for_each and so on.
The structure of the entire bridge delegate is shown below:
Signal < >-> * Interface ^ | Implementation < Receiver >-> Receiver
We have built an Interface/Implementation bridge to connect Singal and Receiver, so that we can effectively separate the direct coupling between the two sides. The demonstration with our previous Socket class is as follows:
Struct Socket {Signal OnRecv;}
A Receiver can be a function such as void OnRecv1 () or a Functor:
Struct OnRecv2_t {void operator () ();} OnRecv2
We can use this bridge delegate like this.
Socket x; x.OnRecv.ConnectSlot (OnRecv1); / / or x.OnRecv.ConnectSlot (OnRecv2 ())
When x.OnRecv () is called when the message is generated, the user-specified OnRecv1 or OnRecv2 will respond.
Let's look at how to implement this bridge: first, an abstract class
Struct DelegationInterface {virtual ~ DelegationInterface () {}; virtual void Action () = 0;}
Then there is the template class Impl:
Template < class T > struct DelegationImpl: public DelegationInterface {T _ FO; DelegationImpl (T _ S): _ FO (_ S) {} virtual void Action () {_ FO ();}}
Notice in the illustration above that the DelegationImpl class is associated with Receiver, that is, the Impl class knows all the Receiver details, so he can calmly call Receiver (). Notice the inheritance relationship again, by the way, an Action function of virutal! Using polymorphic properties, we can instantiate DelegationImpl classes according to Receiver, but we can take advantage of Interface, which provides consistent access to Action, which is the secret of the whole bridge-- using polymorphic lower layers to isolate details!
Take a look at our Signal class:
Struct Signal {DelegationInterface* _ PI; Signal (): _ PI (NULL) {} ~ Signal () {delete _ PI;} void operator () () {if (_ PI) PI- > Action ();} template < class T > void ConnectSlot (T Slot) {delete _ PI; _ PI = new DelegationImpl < T > (Slot);}}
Obviously, the signal class makes use of the DelegationInterface* pointer _ PI to call the response function. And it is this wonderful ConnectSlot function that completes all these join operations. That's right! The last time we discussed the template function, it was said that this T type could not be saved, but the problem was avoided with a bridge here. Using the T of the template function as the instantiation parameter of DelegationImpl, everything is solved so simply.
You might protest that I made a big detour and went back to the inheritance / polymorphism that bothered me in the first place. In fact, have you found that our Singal/Bridge Delegation/Receive system is a fixed set of things, you do not need to inherit to deal with overloading in practice, you just need to Connect to the correct Slot. This can also be regarded as a kind of partial implicit inheritance.
Next we will discuss the performance consumption of this bridge delegate as well as its extensions and limitations.
Further study on Bridge entrustment
After reading the bridge delegate above, you may be a little skeptical about his performance. You need an interface pointer and a functor class / function pointer. When you call it, you need to check vtable once, and then make the operator () call again. In fact, these consumption is not very large, the class structure of the whole bridge delegate is simple, compared with the previously mentioned methods such as inheriting the entire class, the overhead is relatively small, and it is more general and type safe than function pointers. Most importantly, the Signal just now can be easily rewritten to Multi-Cast Delegation, that is, a signal triggers multiple responses-- just change the DelegationInterface* pointer inside the Singal to a pointer queue.
However, the bridge delegate we just implemented can only receive function pointers and functor, not member functions of another class, which is sometimes a very useful action. For example, set the response of the OnClick event of a button Button to a Show method of MsgBox. Of course, there are many other ways to use MsgBox so that you don't have to confine yourself to treating MsgBox as a functor.
We need to rewrite the whole bridge to achieve this function, where you need to know something about pointers to member functions.
/ / the new version of bridge delegate, which can receive the member function of the class as a response struct DelegationInterface {virtual ~ DelegationInterface () {}; virtual void Run () = 0;}; template < class T > struct DelegationImpl: public DelegationInterface {typedef void (pF_t * _ pF_t) () / / pointer type DelegationImpl (T* _ PP, _ pF_t pF) to class T member function: _ P (_ PP), _ PF (pF) {} virtual void Run () {if (_ P) {(_ P-> * _ PF) ();} / / member function call, very awkward (_ P-> * _ PF) ();} T* _ P / / Receiver class _ pF_t _ PF; / / points to a member function of Receiver class}; struct Signal {DelegationInterface* _ PI; Signal (): _ PI (NULL) {} void operator () () {if (_ PI) _ PI- > Run () } / / the new ConnectSlot needs to specify a class and a member function of this class, template < class T > void ConnectSlot (T & recv, void (T recv * pF) ()) {/ / pF this parameter is really awkward _ PI = new DelegationImpl < T > (& recv, pF);}}
Note: the pF parameter type of the ConnectSlot method is very complex, and it can also be simplified by pushing this type detection to the DelegationImpl class instead of doing it here in Connect? The compiler can correctly identify. For templates, many complex parameter types can be replaced with a simple type without paying attention to the details, as shown above with an F instead of void. Sometimes it can improve readability, sometimes it's the opposite.
Template < class T, class F > void ConnectSlot (T & recv, F pF) {PI_ = new DelegationImpl < T > (& recv,pF);}
How to use this new edition? it's very simple. For example, your MsgBox class has a member function Show, which you can use as a response function:
MsgBox box; Socket x; / / Socket is also the same as the old version x.OnRecv.ConnectSlot (box, & MsgBox::Show)
Note that the above reference to the member function pointer must not be written as box.Show, hehe, I hope you still remember that the member function is something common to the class, not a private product of an instance. You might as well use your brains a little further, combine the new version of Signal with the old version of Signal, and you can get a super-powerful Delegation system.
Comments: it is true that you can easily replace handlers dynamically with signal, but this is at the expense of taking up a signal space in each object for each message that may be processed. Also, I can't remember when I've seen applications that need to change handlers dynamically. Even if there is, you can do it yourself in override's virtual function, which is troublesome, but it is possible. In addition, the above code is not standard enough, and the identifier at the beginning of the underscore and uppercase letter is reserved for the implementation of the language.
This is the end of the content of "how to implement C++ delegation and message feedback template". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.