📜 ⬆️ ⬇️

We write our std :: function (boost :: function)

The std :: function and boost :: function classes are high-level wrappers for functions and functional objects. Objects of such classes allow you to store and call functions and functors with a given signature, which is convenient, for example, when creating callback calls (for example, we can register several handlers, and these can be both normal functions and objects with a specific operator =)

If you are interested in how this functionality is implemented, then I ask for the cat


Brief introduction, examples of use


If you are not familiar with boost :: function and std :: function, you can read them here and here.
std :: function is included in the c ++ 11 standard, and the gcc-4.7 and msvc-2012 compilers support it (perhaps earlier versions also have support). In principle, within this article, the implementation from boost and the standard can be considered completely identical, so you can use any of the implementations
')
Actually use example:
int func1() { return 0; } struct callable { int operator() () { return 1; } }; ///... boost::function<int (void)> x; x = func1; int res = x(); //  0    callable c; x = c; res = x(); //  1    


Go to the implementation itself.


We will do the implementation itself in several stages:

The simplest implementation, the concept of Type erasure

The implementation of this class is based on the Type Erasure pattern, more readily available here Its purpose is that we can hide behind a single interface various entities (objects, pointers, etc.) that provide similar capabilities (for example, to call functions with three arguments). Type erasure can also be represented as a bridge that links the runtime polymorphism (runtime polymorfism) and the compile-time polymorphism (compile-time polymorfism).

So, go to the implementation.
We will use variadic templates from the standard C ++ 11. For example, gcc supports this functionality since version 4.3 , so you can safely use it.

Let's not be original and call our class function. It is obvious that the class will be a template, it is also obvious that it will have one parameter of the template - the signature (type) of the function being called. The overall implementation of the template is missing, all work will occur in a partial specialization of the template. Partial specialization is needed so that we can use the types of the arguments and the return value from our signature.
The implementation itself:
 template <typename UnusedType> class function; template <typename ReturnType, typename ... ArgumentTypes> class function <ReturnType (ArgumentTypes ...)> { public: function() : mInvoker() {} template <typename FunctionT> function(FunctionT f) : mInvoker(new free_function_holder<FunctionT>(f)) {} ReturnType operator ()(ArgumentTypes ... args) { return mInvoker->invoke(args ...); } private: class function_holder_base { public: function_holder_base() {} virtual ~function_holder_base() {} virtual ReturnType invoke(ArgumentTypes ... args) = 0; }; typedef std::auto_ptr<function_holder_base> invoker_t; template <typename FunctionT> class free_function_holder : public function_holder_base { public: free_function_holder(FunctionT func) : function_holder_base(), mFunction(func) {} virtual ReturnType invoke(ArgumentTypes ... args) { return mFunction(args ...); } private: FunctionT mFunction; }; invoker_t mInvoker; }; 

The function class defines the () operator corresponding to the function signature and transfers control to the invoke method from the function_holder_base class. This class has the invoke virtual function, which also matches the specified signature (except for the implicit parameter this).
Also, the function class has a template constructor that takes one argument; in this constructor, a descendant of the function_holder_base class free_function_holder is created. This descendant is a template class — it retains the argument passed to it (usually, it is a functor or a function pointer). It also defines the invoke method, which calls the saved functor with the given arguments.

Here it is necessary to note several features of templates in C ++:


In principle, we got a workable analogue of std :: function and boost :: function, we can easily write the following code:
 int func2(const int * x, int y) { return (*x) + y; } ///... typedef function<int (const int * , int)> int_function_with_two_args_t; int_function_with_two_args_t f2(func2); int x = 10; cout << "calling function with signature int (const int * , int): " << f2(&x, 20) << endl; 

Moving on to improvements in our class:

Copy the behavior of a regular function pointer - an assignment statement and a copy constructor

In order to copy or assign an object, we must be able to copy (clone) a pointer to the base class function_holder_base. To do this, we expand the interface of this class as follows:
  class function_holder_base { public: function_holder_base() {} virtual ~function_holder_base(){} virtual ReturnType invoke(ArgumentTypes ... args) = 0; virtual std::auto_ptr<function_holder_base> clone() = 0; private: function_holder_base(const function_holder_base & ); void operator = (const function_holder_base &); }; 

We make our class uncopyable (we declare the corresponding operator and constructor in the private section), and declare the clone method so that the heirs themselves determine the correct cloning strategy.

Also, assignment statements and a copy constructor are added to the function class:
  function(const function & other) : mInvoker(other.mInvoker->clone()) {} function & operator = (const function & other) { mInvoker = other.mInvoker->clone(); } 

Here we use auto_ptr and its destructive assignment.

It remains to write the implementation of the clone method in the heir - free_function_holder:
  typedef free_function_holder<FunctionT> self_type; virtual invoker_t clone() { return invoker_t(new self_type(mFunction)); } 


That's all, now our class behaves like a normal function pointer, and we can do this:
 int func1() { return 0; } ///... typedef function<int (void)> int_function_t; int_function_t f1(func1); cout << "calling function with signature int (void): " << f1() << endl; int_function_t f2; f2 = f1; cout << "calling function after assignment operator with signature int (void): " << f2() << endl; int_function_t f3(f2); cout << "calling function after copying ctor with signature int (void): " << f3() << endl; 


Go to the final part:
Add support for pointers to member functions

I got support for method pointers rather limited: you can pass an object to which a pointer to a method is applied only by value (and I would also like a link (const and not const) and a pointer (const and not const)), but in principle for An example of this will be sufficient.

When using boost (std) and our function implementation, we adhere to the rule that the first argument must be an object to which the pointer to the method is applied, respectively, the arguments are now divided into two types: the object itself, and the method arguments. Accordingly, we have a guarantee that the number of arguments is strictly greater than 0, and we will use this further:
  template <typename FunctionType, typename ClassType, typename ... RestArgumentTypes> class member_function_holder : public function_holder_base { public: typedef FunctionType ClassType::* member_function_signature_t; member_function_holder(member_function_signature_t f) : mFunction(f){} virtual ReturnType invoke(ClassType obj, RestArgumentTypes ... restArgs) { return (obj.*mFunction)(restArgs ...); } virtual invoker_t clone() { return invoker_t(new member_function_holder(mFunction)); } private: member_function_signature_t mFunction; }; 

Implementing a constructor that takes a pointer to a method is trivial:
  template <typename FunctionType, typename ClassType> function(FunctionType ClassType::* f) : mInvoker(new member_function_holder<FunctionType, ArgumentTypes ...>(f)) {} 

We have made another heir function_holder_base specifically for method pointers. Here the variadic templates feature is used: a variable number of types (which is specified by the ellipsis) can be split into a fixed part, and the remainder of a variable length (in which the number of types is smaller by the size of the fixed part). In the constructor, we pass in the template parameters a fixed part consisting of one type — the function signature, and an arbitrary part — everything else (these are the types of all arguments), and in implementing member_function_holder, we require that the fixed part consist of two elements — the function signature, class, in which our method is located, and the arguments to call directly (here we just use the above guarantee that the number of all arguments is strictly greater than 0). Thus, we save the pointer to the method in the constructor, and call it in the implementation of the invoke method.

Separately, I would like to say about the extremely inconvenient and unintuitive way of declaring the type of "pointer to a method" and about the method of calling such a method. In this regard, in the C ++ FAQ there is a warning about how to minimize the amount of headache when working with method pointers (I googled for half an hour how to write this expression correctly)


Here is an example of use:
 struct Foo { int smth(int x) { return x + 1; } }; ///... typedef function<int (Foo, int)> member_function_t; member_function_t f1 = &Foo::smth; Foo foo; cout << "calling member function with signature int (int): " << f1(foo, 5) << endl; 


Conclusion


Thanks to the support of the variadic templates implementation, the function turned out to be quite concise, in the same boost because of the need to support old compilers (which do not have variadic templates), this functionality is implemented using boost.preprocessor (and has a limit on the number of arguments - 10 by default, you can change by defining the appropriate define: BOOST_FUNCTION_MAX_ARGS). Roughly speaking, the implementation is done for functions with one argument, and then “cloned” into a larger number of arguments using preprocessor magic.

The complete example is here.

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


All Articles