[C ++] Implementing the delegate Model

Source: Internet
Author: User

I am very impressed with the. NET delegate model. With delegation, the observer mode can be quickly implemented, eliminating the need to write a lot of complicated and repetitiveCode. Unfortunately, C ++ does not provide such a model. To achieve similar purposes, we need to inherit a class and override the virtual method. This method requires a lot of code, low Efficiency (we should be able to understand it if we have used MFC ). However, in the face of powerful C ++, nothing is impossible. Many people have studied this problem and implemented various delegate models, the most famous one is fastdelegate, which is proposed in member function pointers and the fastest possible C ++ delegates ). This model is characterized by "fast", so it is inevitable to rely on the specific implementation of the compiler, althoughArticleFinally, it indicates that the model has passed tests in most compilers, and I am still not very worried about this. If the compiler has changed the implementation method after upgrade, this model is not suitable for use. Moreover, due to my limited level and laziness, I do not want to go into the specific implementation methods of each compiler. What I want is a model that complies with the c ++ standard and has nothing to do with the compiler, regardless of whether it is "fast" or not ". After constant exploration, I finally wrote such a delegated model. I would like to share with you the implementation principles of this model. (Of course, If you think fastdelegate has met your needs and don't worry that it depends on the compiler, you can ignore this article)

 

Member function pointer operation

Before getting started, we will first introduce the member function pointer, which is very different from the non-member function pointer operation method. There is such a class:

Class A {public: void func (INT ){... }};

To obtain the pointer to the func function, you must do this:

 
Void (A: * pfunc) (INT) = & A: func;

: * Is a special operator, indicating that pfunc is a pointer pointing to a member function. The address for getting a member function cannot be obtained through the class object. It must be obtained through the class name as above, and the get address operator (&) must be added (&).

 

So how can we call this function through the member function pointer? All member functions have an implicit this parameter, indicating the object to be operated by the function. Now we only get the pointer to the function, and an object is missing as the this parameter. To achieve this goal, you need to first create an object and then call the member function pointer through this object:

A A; (A. * pfunc) (10); A * pA = & A; (Pa-> * pfunc) (11 );

The first method is to call the object itself. The second method is to call the object through the Object Pointer. Both methods have the same effect .. * And-> * are special operators, so you don't have to worry about them as long as you know they are only used to call member function pointers.

 

Step 1: Use a class template

Through the above introduction, we know that to call a member function, it is not enough to have only a member function pointer. We also need an object pointer, so we need to bind the two together with a class. Because the object type is infinite, the class template must be used here:

 
Template <typename T> class delegatehandler {public: delegatehandler (T * PT, void (T: * pfunc) (INT): m_pt (PT), m_pfunc (pfunc) {} void invoke (INT value) {(m_pt-> * m_pfunc) (value);} PRIVATE: T * m_pt; void (T: * m_pfunc) (INT );};

You can use this template as follows:

 
A A; delegatehandler <A> AH (& A, & A: func); Ah. invoke (3); B; delegatehandler <B> BH (& B, & B: method); // The Declaration of B: method is the same as that of a: func. invoke (4 );

 

A problem occurs here: what if you want to call a non-member function? The preceding class template cannot call non-member functions, but this problem can be solved by using the special template:

 
Template <> class delegatehandler <void> {public: delegatehandler (void (* pfunc) (INT): m_pfunc (pfunc) {} void invoke (INT value) {(* m_pfunc) (value) ;}private: void (* m_pfunc) (INT );};

The usage is the same:

Delegatehandler <void> H (nonmemberfunc); // void nonmemberfunc (INT); H. Invoke (5 );

 

Maybe you have a question: non-member functions do not need to bind function pointers and object pointers. Why do we need to use a class to wrap function pointers? After reading the following content, you will naturally understand it.

 

Step 2: Use Polymorphism

For a single-object delegate, using the above Code may be enough. But of course I want more than that. I want to delegate multiple targets. Multi-objective delegation is actually a container in which multiple objects can be stored. Each object is called in sequence when the delegate is called. Objects in the container should be of the same type, so that they can be placed in a strongly typed container. The delegate caller should not know the specific call target, therefore, these objects should also hide details. Unfortunately, the class templates implemented in the previous step do not have these capabilities. delegatehandler <A> and delegatehandler <B> are different types and cannot be placed in the same container, to call them, the caller must also know the type of the call target.

 

The method to solve this problem is to use polymorphism so that all the delegate Target classes inherit a public interface. The caller only calls this interface, in this way, you do not need to know the specific type of each target. The following is the definition of this interface:

Class idelegatehandler {public: Virtual ~ Idelegatehandler () {}virtual void invoke (INT) = 0 ;};

Then let delegatehandler inherit the interface:

 
Template <typename T> class delegatehandler: Public idelegatehandler {...} Template <> class delegatehandler <void>: Public idelegatehandler {...}

Now you can put various types of delegatehandler in the same container and call it in the same way:

A A; B; delegatehandler <A> AH (& A, & A: func); delegatehandler <B> BH (& B, & B: method ); delegatehandler <void> VL (nonmemberfunc); STD: vector <idelegatehandler *> handlers; handlers. push_back (& AH); handlers. push_back (& BH); handlers. push_back (& VH); For (Auto it = handlers. cbegin (); it! = Handlers. Cend (); ++ it) {(* It)-> invoke (7 );}

 

Step 3: Use macros

I don't know if you have noticed that there are so many codes written above, just to implement a void returned value, there is an int parameter delegate! If you want to implement more types of delegation, the above Code will be repeated many times. Fortunately, C ++ has a macro, which can help us quickly generate a large amount of code. However, the definition of this macro is not that simple, and I have paid a lot for it. The following describes the process of exploration. If you don't want to read me, you can jump to the next step to view the ready-made code.

 

We all know that the declaration of a function parameter can only be a type without a name. However, to use a parameter in a function, the parameter must have a name. For example:

Void invoke (INT) {// The parameter cannot be used} void invoke (INT value) {// The parameter can be used by the value name}

In addition, you can only use the name when calling a function, but not the type:

 
Int value = 10; invoke (value );

 

These problems seem obvious and are not worth mentioning at all, but these are the key to definition macros. In the beginning, I thought macro should be used like this:

 
Delegate (void, delegatehandler, Int, INT );

There is no doubt that variable parameters should be used from the third parameter in its definition, like this (only part of the definition is truncated ):

# Define delegate (rettype, name ,...) \... Rettype invoke (_ va_args _) {\ return (* m_pfunc) (_ va_args __);\}\...

The expanded code is as follows:

 
... Void invoke (INT, INT) {return (* m_pfunc) (INT, INT );}...

 

This is obviously incorrect, even if the parameter name is added when the delegate is defined. The problem is that the declaration method of function parameters is different from the calling method, and we cannot split _ va_args _ to solve the problem. We cannot add a name for the parameter, the parameter name cannot be removed.

 

In this case, we use two _ va_args __, one for function parameter Declaration and the other for calling. For example, the first _ va_args _ should be like this:

Int A, int B

The second _ va_args _ should be like this:

 
A, B

This should be the case after the macro scale:

 
... Void invoke (int A, int B) {return (* m_pfunc) (a, B );}...

This is correct. However, this brings about a new problem: only one variable parameter can be used in a macro. The solution is to use another macro to generate the two _ va_args __! Well, I will not talk nonsense anymore. I will give the code directly, and the code is better than my expression ability.

# Define declare_params (...) _ va_args __# define declare_args (...) _ va_args _ // delegation of 0 parameters # define delegate0 (rettype, name) \ declare_delegate (rettype, name, declare_params (void ),) // One Parameter delegate # define delegate1 (rettype, name, P1) \ declare_delegate (\ rettype, \ Name, \ declare_params (P1 a), \ declare_args ()) // delegate the two parameters # define delegate2 (rettype, name, P1, P2) \ declare_delegate (\ rettype, \ Name, \ declare_params (p1 A, P2 B), \ declare_args (a, B) // delegate the three parameters # define delegate3 (rettype, name, P1, P2, P3) \ declare_delegate (\ rettype, \ Name, \ declare_params (P1 a, P2 B, P3 C), \ declare_args (A, B, C )) // delegate the four parameters # define delegate4 (rettype, name, P1, P2, P3, P4) \ declare_delegate (\ rettype, \ Name, \ declare_params (P1, p2 B, P3 C, P4 D), \ declare_args (A, B, C, D) // Delegate of five parameters # define delegate5 (rettype, name, P1, p2, P3, P4, P5) \ Declare_delegate (\ rettype, \ Name, \ declare_params (P1 a, P2 B, P3 C, P4 D, P5 E), \ declare_args (a, B, c, d, e) // delegate the six parameters # define delegate6 (rettype, name, P1, P2, P3, P4, P5, p6) \ declare_delegate (\ rettype, \ Name, \ declare_params (P1 a, P2 B, P3 C, P4 D, P5 E, P6 F), \ declare_args (a, B, c, d, e, f )) // delegate the seven parameters # define delegate7 (rettype, name, P1, P2, P3, P4, P5, P6, P7) \ declare_delegate (\ rettype ,\ Name, \ declare_params (P1 a, P2 B, P3 C, P4 D, P5 E, P6 F, P7 G), \ declare_args (A, B, C, D, E, f, g) // delegate the eight parameters # define delegate8 (rettype, name, P1, P2, P3, P4, P5, P6, P7, P8) \ declare_delegate (\ rettype, \ Name, \ declare_params (P1 a, P2 B, P3 C, P4 D, P5 E, P6 F, P7 G, p8 H ), \ declare_args (a, B, c, d, e, f, g, h) # define declare_delegate (rettype, name, Params, argS) \ class I # name {\ public: \ virtua L ~ I # NAME () {}\ virtual rettype invoke (Params) = 0 ;\}; \ template <typename T> \ Class Name: Public I ##name {\ public: \ Name (T * ptype, rettype (T: * pfunc) (Params) \: m_ptype (ptype), m_pfunc (pfunc) {}\ rettype invoke (Params) {\ return (m_ptype-> * m_pfunc) (ARGs) ;\}\ PRIVATE: \ t * m_ptype; rettype (T: * m_pfunc) (Params );\}; \ template <> \ class name <void>: Public I # name {\ public: \ Name (rettype (* pfunc) (Params) \: m_pfunc (pfunc) {}\ rettype invoke (Params) {\ return (* m_pfunc) (ARGs) ;\}\ PRIVATE: \ rettype (* m_pfunc) (Params );\}

Note that a semicolon is missing at the end. This is intended to force a semicolon to be added when the delegate is defined. This macro-defined method has a limit on the number of parameters. I can define up to eight parameters here. To support more parameters, you need to write more code. In fact, I think the eight parameters are enough. The functions with more than eight parameters are not well designed and should be reconsidered.

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.