Boost source code analysis: generic function pointer Boost :: function (revision)
Liu Weipeng / article
C Louvre (http://blog.9cbs.net/pongba)
Disclaimer: Published in September 2003, this article is extremely changed, especially in the middle and late part :-)
Prelude
As you know, the BOOST library is complete, and has industrial strength libraries, and many C authoritative participation has reached the degree of Dengfeng. Especially the powerful power of genericity is played, and it is awkward.
However, weak water is three thousand, we only take a scoop. Below, I tried to start from the most simple world, one step, one step, led you into the source of the source code, to explore the fine structure inside the Boost :: Function (hereinafter referred to as function).
Typically, in a simple case, the call to the function is simple and intuitive, like this:
INT fun (int Someval);
Int main () {
Fun (10);
}
However, you may need to save the function pointer on a moment and call it at another time, like this:
INT Fun (int);
Typedef int (* func_handle) (int);
Int main () {
FUNC_HANDLE FH = FUN;
... // Do Something
FH (10);
}
However, if the FUN is in the form of Void Fun (int)? As you can see, Fun may have countless forms. If you have a corresponding FUNC_HANDE for each form of Fun, the programmer will focus, it will be bloated, and the code may become bloated and ugly, even if Fun is What about the imitation function?
Fortunately, C generics can make the code elegant and exquisite, facing countless possibilities, generics is the best choice.
Therefore, you just need a generic template class that can save the function pointer (corresponding to the Command mode) because generic programming has a congenital advantage - can use the power of the compiler in the compile time according to the type of information according to the user Millions of incarnates (excited), so a generic class can have an infinitely a body, that is, something that can save unlimited variety of possible functions or similar functions (eg, imitation functions). This class (in the BOOST library is function) and the function pointer should have some advantages:
¨ The same Function object should be able to accept all functions and imitation functions compatible with it, for example:
INT f1 (int); // This is a function, form is int (int)
Short F2 (Double); // This function is short (double)
Struct functor // This is an imitation function class, form is int (int)
{
INT Operator () (int) {}
}
Functor F3; // Create an imitation function object
Boost :: function
Func = f1; // Accept F1
Func (10); // Call F1 (10)
Func = f2; // can also accept SHORT (double) type F2
Func (10); // Call F2 (10)
Func = f3; // can also accept the imitation function F3
Func (10); // Call F3 (10)
¨ Function should be able to work with parameter bindings and other Function-Construction libraries. For example, Function should also accept the imitation function returned by std :: bind1st. This is actually guaranteed by the first point.
¨ When the acceptable emotion function object is called, Function should have a pre-expected behavior. Obviously, the first point is our focus, so-called form is compatible, that is, for:
R1 (T0, T1, T2, ..., TN) => functionType1
R2 (p0, p1, p2, ..., pn) => functionType2
Two types of functions (broad), as long as they are satisfied:
1. R2 can be implicitly converted to R1
2. All Ti can be implicitly converted to Pi (i take 0, 1, 2, ...)
Then, say, boost :: function
Behind you will see, Boost :: Function has achieved this through the so-called Invoker and blocks the operation assignment of the functionally incompatible function.
Adventure
Ok, ready, we have to leave, conduct an explore in the source code world.
First look at the simplest use of a Function:
INT g (int); // In order to make the code simple, assume that G has defined, the subsequent code will be so
Function
f (0);
Interconnected - R (T1, T2, ...) function type
Although this room has not been early, but in order to make you won't be confused, this is obviously necessary. Please keep patient.
Maybe you will feel unfamiliar with the template parameter int (int), in fact it is a function-type-function G exact type is int (int), and we usually see the function pointer type INT (*) (int) (INT) ) Is the type of & g. Their differences and links are: When the G is copied as a value (for example, by value transmission), the type is degraded into int (*) (int), that is, degrading from the function type For the function pointer type - because of the semantic say, the function cannot be "copy", but the address value of the function pointer is a copy. On the other hand, if G is bound to a reference, its type does not degenerate, and the function type is still maintained. E.g:
Template
Void test_func_type (t ft) // Press value transfer, type degradation
{
Static_cast
}
INT G (int); // Function g, the type G type Int (int)
Test_func_type (g); // Note, not & g, the type of parameter G is degraded into function pointer type
INT = g; // Note, not "= & g", because binding to reference, the type does not degrade
Of course, such code cannot be compiled because static_cast <> obviously does not convert a function pointer to Int, but we want it to be compiled so that we can peek to the type of parameter ft passed by value. From the compilation error, we see that the type of FT is int (*) (int), that is, in the process of paying the value, G's type degradation is the function pointer type, becoming the type of & g. The type of REF_T is referenced, and the reference binding does not cause type degradation. Note that the function type is an extremely special type. It is mostly degraded to the function pointer type in most, in order to meet the copy semantic, only the original type can only be maintained when the reference binding is facing. Of course, for Boost :: function, it always copies the value.
Continuance
Ok, go back, we have more zone to explore.
Function
Template TypeName Allocator = ... > // allocator is not focus, so it does not introduce Class function; In fact, the Function class is just a thin overhead, which really acts on the offset version. For function Excerpted from: "Boost / Function / Function_Template.hpp" Template Class Function : Public Function1 { Typedef Function1 Typedef function selfType; Struct clear_type {}; // You will immediately see the type definition of this type of embarrassment PUBLIC: Function (): base_type () {} // Default Construction Template Function (Functor F, Typename Enable_if < (ICE_NOT <(IS_SAME int > :: type = 0): Base_Type (f) {} Function (Clear_Type *): Base_type () {} // This constructor is explained below Self_type & operator = (const self_type & f) // The same type of Function object should be assigned { Self_type (f) .swap (* this); // swap skills, details See "Effective STL" Return * this; } ... } ENABLE_IF You must have a doubtfulness of the length of the eNable_if <...> that appears in the template constructor. In fact, its role is very simple, that is: when the user constructs: Function At the time, the "Enable_IF) constructor is kicked from the candidate of the overload resolution. The result of the overload resolution is selected: the third constructor: Function (CLEAR_TYPE *): BASE_TYPE () {} Thereby the default construction is performed. It is said that the length is: When F is type -functor-- is not int, the constructor is "Enable", and will be selected by the overloaded resolution. However, if the user provides a 0, it is intended to construct a null function pointer, then the function will be kicked from the candidate function of the overload resolution due to the "sfinae" principle. Why has to be this way? Because the constructor is responsible for saving the exact f, it assumes that f is not 0. Who should I choose? The third constructor! Its parameter type is CLEAR_TYPE *, of course, 0 can be assigned to any pointer, so it is selected to perform default constructive behavior. Base class functionn Function's skeleton is these. Maybe you will ask, function as an imitation function class, how can I not overload Operator () - this is a sign of a function! Don't worry, Function has threw these annoying tasks to its base class Functionn, depending on the situation, N may be 0, 1, 2 ..., saying that the specific point is: the type of function given when using function Function will inherit from different base classes - if the user given the function type "R ()" form, only one parameter is only one parameter, then function inherits from function0, and the function type in R (T0) Inherit from Function1, so on. As mentioned earlier, Function is just a layer of overclothes, and all the secrets are in its base class functionn! I don't know if you find that the Function's skeleton is almost unable to use the function type information. In fact, it also throws this information into the base class. In this process, the INT type of chaotic group is disassembled as two separate template parameters to the base class: Template Class Function : public function1 Ok, let's go deep into the base class Function1. Really rich treasure inside. Function1 The source code of Function1 is like this (like above, in fact, some code you can't see, in order not to confuse, I give the code to open the macro.): Excerpted from: "Boost / Function / Function_Template.hpp" Template Class Function1: Public Function_Base // Function_base is responsible for managing memory { ... PUBLIC: TypeDef r result_type; // Return Type Typedef Function1 Self_Type; Function1 (): function_base (), invoker (0) {} // default configuration Template Function1 (Functor Const & F, // Template Construction Function TypenAme Enable_if <...> :: type = 0): Function_base (), INVOKER (0) { This-> assign_to (f); // This is really assigned, and the code of Assign_to is listed below. } Function1 (CLEAR_TYPE *): Function_Base (), Invoker (0) {} // This constructor is explained above Function1 (Const function & f): // copy constructor Function_base (), INVOKER (0) { THIS-> Assign_to_OWN (f); // Dedicated to Assignment assigned between Function } Ø Result_type operator () (t0 a0) const // as a sign of the function! {// below is responsible for calling the pointing function IF (this-> EMPTY ()) Boost :: throw_exception (Bad_Function_Call ()); / / Here, true function calls, use Invoker INTERNAL_RESULT_TYPE RESULT = Invoker (function_base :: functor, A0); Return static_cast } Template Void Assign_to (Functor f) // All constructor calls it! Has multiple overload versions. { // Extract the category of the Functor with a GET_FUNCTION_TAG! TypedEf TypeName Detail :: Function :: Get_Function_Tag This-> Assign_to (f, tag ()); // Take different ASSIGN policies according to different categories of Functor! } GET_FUNCTION_TAG <> can extract the category of the functor, there are several categories below Struct function_ptr_tag {}; // function pointer class Struct function_obj_tag {}; // 函 function object category Struct meMber_ptr_tag {}; // member function category Struct function_obj_ref_tag {}; // With the category of the encapsulated by REF (OBJ), it has a reference semantic Struct stateless_function_obj_tag {}; // Stateless function object Meet all the conditions: HAS_TRIVIAL_CONSTRUctor HAS_TRIVIAL_COPY HAS_TRIVIAL_DESTRUctor IS_EMPTY Imitation function object is called STATELESS And for different function categories, Assign_to has different overload versions, as follows: Template Ø Void Assign_to (FunctionPtr F, Function_PTR_TAG) // This version is for function pointer { Clear (); IF (f) { TypeDef TypeName Detail :: Function :: get_function_invoker1 < FunctionPTR, R, T0> :: Type Invoker_Type; Invoker = & invoker_type :: invoke; // invoke is a Static member function Function_base :: Manager = // Management Policy & detil :: function :: functor_manager Function_base :: function = // Function pointer to function or imitation function object pointer is finally saved here Function_base :: Manager Detail :: function :: make_any_point ((void (*)) (f)), Detail :: function :: clone_functor_tag); // Really copied a function pointer } } ... TYPEDEF INTERNAL_RESULT_TYPE (* Invoker_Type) (Detail :: function ::_pointer, t0); Invoker_type invoker; // Important member, responsible for calling functions! } You may have already gotten the head of this "claver clip", but this is just beginning! Function underlayer storage mechanism Turn your eyes to the ASSIGN_TO function at the end of the code segment of the above code, with two lines of brunette code, assignments to the Manager and Functor members in Function_Base. These two lines of code shoulders the task of saving various function pointers. Manager is a function pointer, which represents the management policy, for example, for function pointers, only one assignment, is saved, but for the imitation function, you have an additional memory, then copy the imitation function to allocate In memory, this completes the saved task. These policies determined according to the category of the function, the ASSIGN_TO function in the above code is a heavy-duty version for the function pointer class, so the manager's policy is not any memory allocation, directly returning to "void (*) ()" (favors The function pointer that is saved in the underlying form in a unified form, which can be seen from the code. It should be noted that for function pointers, function_base does not know what the exact type does not care about the function pointer it wants to save, as long as it is a function pointer, because it always converts the function pointer F to "void (*) () "Type, then saved in the Functor member, Functor member is a Union: Union Any_Pointer { Void * obj_ptr; // Any imitation function object pointer can be used to void * type with static_cast <> Const void * const_obj_ptr; / / Preparation for Const imitation functions Void (* func_ptr) (); // Any function pointer can be used to void (*) () Char Data [1]; } This any_pointer can save all forms of imitation functions and function pointers through secure transformation, carrying the task of saving data at the bottom layer. Function Call Mechanism - Invoker We turned its attention to the bottom of the definition of Function1, which defined its most important member invoker. It is a function pointer. The function points to the function is the call mechanism of Function. Invoker's type is: typedefinal_result_type (* invoker_type) (any_pointer, t0); As mentioned earlier, any_pointer is a Union, you can save any type of function pointer or function object, saved is a function or imitation function of the user registration, t0 is the type of parameter of the function in the ANY_POINTER (for different situations, possibly There will be T1, T2, etc.). This means that Invoker is responsible for calling the function or imitation function provided by the user saved in any_point. So, what function does this function pointer point to this function? - That is to say, when is INVOKER being assigned? We turn your attention to the assign_to function, where there is a statement assigned to the Invoker member, from this person sentence, we can expose all the mystery of Invoker: Invoker = & invoker_type :: invoke; // invoke is a Static member function Please do not confuse this invoker_type and the above function pointer INVOKER_TYPE, this invokeer_type is a partial typedef located in the assign_to function, so hidden the latter (ie the type of invoker_type - invoker member in class) . To the top, you will see the definition of this partial INVOKER_TYPE: TypeDef Typename get_function_invoker1 < FunctionPTR, R, T0> :: Type Invoker_Type; What is GET_FUNCTION_INVOKER1? Obviously, this is a traits, which is embedded :: Type as a different type according to different template parameters, in this case, :: Type's type will be derived as Function_invoker1 Function_INVOKER1 is a class template that is defined as: Template TypenAme R, Typename T0> // Note the template parameters here, will be explained later Struct Function_INVOKER1 { Static R Invoke (any_pointer function_ptr, t0 a0) { FunctionPTR f = reinterpret_cast RETURN F (A0); } } So the assignment of Invoker is ultimately equivalent to: Invoker = & function_invoker1 Function_invoker1 Static int invoke (any_pointer function_ptr, int A0) { // First transform, then call, note that this line of statement has an additional role in explanation INTRPRET_CAST RETURN F (A0); } We can see that in the Invoke function, the real call appears. If the acceptance function is accepted, Function_Obj_invoker1 corresponds to it, the latter is also a similar template, its invoke static member function form is also: Static R Invoke (any_pointer function_obj_ptr, t0 a0); Where function_obj_ptr is a pointer to the imitation function, so its invoke static member function is like this: FunctionObj * f = (functionObj *) (function_obj_ptr.obj_ptr); RETURN (* f) (A0); // Call the user's imitation function The last possibility: What if the member function is accepted? The simple answer is: boost :: function is not a special preparation for member functions! The reason is also very simple, boost :: function as long as the member function is packaged as the imitation function, then treat it as a general imitation function, the specific code is not listed, there is a function template in STL :: MEM_FUN is used The package member function pointer, it returns an imitation function. This function template is also expanded in Boost to allow it to accept member functions of any multiple parameters. Do one, send an additional benefits of -invoker We noticed that the constructor and assignment functions of the function and its base classes are template functions because users may provide functions that may also provide function templates, but the most critical or FunctionTs provide a capability: For function RT1 f (p1, p2); // RT1 (P1, P2) function type, the RT1, P1, P2 here, is assumed, which is a general symbol Function f_ptr = & f; // Type is not compatible, error! This will cause compilation errors, and the error occurs in the Invoke static member function. Below I explain why. I think your three template parameters for Function_Invoker1 are still confused. We will review their statements again. Template Typename R, Typename T0> Struct Function_INVOKER1 We have to turn your gaze to the assign_to template function, in which it is like this using Function_INVoker1: Typedf Typename Detail :: Function :: Get_Function_INVOKER1 Here, the three template parameters given in the functionPTR, R, and T0 will be passed to Function_Invoker1, so what is the three template parameters for the above error examples? First of all, we can easily see that FunctionPtr is the template parameter of the Assign_to template function, that is, the type of function or imitation function passed, in our error example, the type of function f is RT1 (P1, P2), so FunctionPTR = RT1 (*) (P1, P2) R, T0 is the template parameters given when the user is instantified the Function template. We write Function R = RT T0 = P So, for our error example, INVOKER_TYPE type is: Function_invoker1 For such a Function_Invoker1, its internal Invoke static member function is instantiated: Static RT INVOKE (any_pointer function_ptr, p A0) { RT1 (* f) (p1, p2) = // functionorptr f = ReinterPret_cast Return F (A0); // is wrong! Different, f, f, to accept a P-type parameter? The compiler will hit it here. // Another implicit check of this row statement is the return value type match, f (...) returns RT1, and the invoke must return RT } Take a look at the last row statement, all inspections are there - we finally check the "delegate" to the type system of C underlying. Is it very exciting? Although in the form of the template, it seems that we don't care about the parameters given by the user, it seems that users can put any functions or imitation functional numbers, but once the Invoker's assignment is attached, they have to instantiate Invoke. Static member functions, where: RETURN F (A0); I will expose the problem! This type of check is delayed to the end, and when it has to be performed, the type of C underlying type system is indeed very wonderful - it does not use the type information to use type information in the assign_to function, but we have There is no loss of any type of security, and everything will eventually flee the test of the type system of C underdeveloped! How does Function treat member functions For member functions, the overload version of Assign_to is only one line: this-> assign_to (MEM_FN (F)); MEM_FUN (f) returns an imitation function, which encapsulates the member function f, and everything is not different from the imitation function. Regarding the details of MEM_FUN, this will not be said, you can refer to the implementation in STL, I believe it is easy to understand, here is simple to remind, the effect of member function packages is like this: R (C :: *) (T0, T1, ...) -> R (*) (C *, T0, T1, ...) or R (*) (C &, T0, T1, ... ) SAFE_BOOL idiom As you know, for function pointer FPTR, we can test it: if (fptr) ..., Function should also provide this feature, however, if directly overloading Operator Bool () will cause the following code to become legal : Function BOOL B = f; This is obviously unpleasant, so function replaces it with another clever manner, the so-called SAFE_BOOL usual technique, which is in the function of the internal source code as follows: Struct Dummy {Void Nonnull () {}; Typedef void (Dummy :: * Safe_Bool) (); // Make sure SafeBool cannot be transformed into any other type! Operator Safe_Bool () Const {RETURN (this-> EMPTY ())? 0: & Dummy :: Nonnull;} This way, when you write if (f), the compiler will find Operator Safe_BOOL (), thereby converting F to SAFE_BOOL, which is a pointer type, and if statement will correctly determine if it is empty. And when you try to assign F to other types of variables, it will be rejected - because Safe_Bool cannot convert other types. GET_FUNCTION_TAG <> GET_FUNCTION_TAG <> is used to extract the category of the function (Category), each category is already listed in the source code, as for how it extracts, this is not very big, there is a little need to remind: Function pointer type is also The type of pointer, this sounds completely, but considers such code: Template Template Std :: cout << is_pointer That is, INT (*) (int) can match T *, and T is INT. Last detail 1. I didn't give the source code of Function_Base. In fact, it is very simple. It is the most important member of the Union Any_Pointer type. Detail :: function :: any_pointer function; // Used for unified saving function pointer and imitation function object pointer 2. I didn't give the information of Functor_Manager, in fact it doesn't have too much relationship with the implementation of Function, which is responsible for copy and delete function objects, if necessary. So I will slightly, its source code is in: "Boost / Function / Function_Base.HPP". 3. The source code I gave is a lot of macro that will be dazzling after the macro show, the actual code, and the macro is another wonderful world. The Boost library has went many visible code through those macro province. As the function parameters are different, those macro will expand Function2, Function3 ... all versions. This article only studies int (int) type, others are only the change of the number of parameters. After macro expansion, the Function's offset version will be: Template Class Function Template Class Function {...}; Template Class Function {...}; Some versions, a total of boost_function_max_args 1 version, boost_function_max_args is a macro, indicating that you can accept how many parameters can be accepted, and you can redefine this macro as a new value to control the function of Function. The maximum value of the number of function parameters. The names of Function0, Function1, Function2 are also exhibited by macro. About the Author: Liu Weipeng is a Nanda's student, loves everything about C , and also likes .NET, although C # is bad, but .net is really good, I am intending to write a series, deep digging. The architecture and implementation of NET, but because it is busy with postgraduate, I don't know what year, I can start ... Changed at 2004-10-2 23:24 :-)