Under the influence of the
previous article, I propose to continue the theme of creating your own implementation of such a useful idiom as
function in C ++, and consider some aspects of its use.
What is function and why write another
I will try to give a brief free formulation:
this is an object that has semantics of meaning and allows you to bring free functions and member functions of classes to a single call interface . If necessary, the object saves the context of the call, talking about
this , which is relevant for member functions of classes.
Advanced implementations, such as
boost , offer such interesting tools as adapting functions with an inappropriate signature to the required call interface, as well as the possibility of associating arguments with parameters at the time of initialization. But, in my opinion, such opportunities are beyond the scope of the main theme, noticeably inflate the implementation, but at the same time, they are far from always necessary. Such tools are certainly convenient and sometimes even useful, but, in my subjective opinion, they complicate the reading and debugging of the code. Let your own implementation be somewhat simpler, while possessing such advantages as relative conciseness and simplicity.
Motivation to continue the search
Thus, the statement of the problem: a class is required that is parameterized by the signature of the function that defines the call interface, which allows you to associate and later call both free functions and member functions of classes, saving the call context for the latter. Returning to the solution from the
previous article , does it meet the specified requirements, what can be noticed in it, which can be corrected? The solution does not preserve the calling context for the member functions of the classes and, thus, loses its universality of application. Inside, virtual functions are used, inheritance and dynamic memory allocation, which, as will be seen later, is not necessary, can be made easier. I will make a reservation that the dynamic allocation of memory is considered to be the main aspect that needs improvement. If it is necessary to link and transfer a function somewhere in a code with a high call frequency, which requires at least high performance, the first thought that arises when using a certain
function , but will it not be inside memory allocations? .. fixed buffer of the required conservative size, use the
placement in place , do not forget about the correct alignment, but - there is another way.
Its implementation
To solve the problem, the language C ++ 11 is used. Code tested in
Xcode 4.5.2 environment . The implementation of C ++ 11 in Visual Studio 2012 is late, but when installing
November 2012 Compiler CTP, you can get the level of support required for building the example, although there is no full practical application of speech. In the code there is a slight reverence towards VS2012 for compatibility. If necessary, the solution can be rewritten for C ++ 03 at the cost of a significant increase in the amount of code, but retaining the main advantages.
I'll start from the end. What a fancy usage example might look like:
#include <iostream> #include <vector> #include "function.hpp" int foo(int v) { std::cout << "foo: " << v << std::endl; return v; } struct bar { int baz(int v) { std::cout << "bar::baz: " << v << std::endl; return v; } }; int main() {
It can be seen that objects can be located in the same container, pointing to different free functions and member functions of different classes. The only requirement for them is a single format of parameters and a type of return value. Because of the implementation features, the macro
BC_BIND is used for the less short and uniform binding record. It is interesting to note that the macro takes a different number of arguments, one and two, but the preprocessor does not support overloading macros of the same name by the number of arguments, however, it supports the transmission of a variable number of arguments through an ellipsis, which, combined with some magic, allows simulation of the overload by the number of arguments. I will not go further, it’s not difficult at all to figure it out on my own, but I’ll notice that Visual C ++ has manifested itself with its special vision of the standard, and for compatibility with its preprocessor it took a little more powerful magic.
The story goes to the most interesting. How can you store different pointers to different functions and member functions in a uniform way, and without dynamic allocations? The standard does not say anything definite about the size of the pointers to functions and their structure, which would allow them to bring to a common denominator. If for pointers to free functions at least POSIX
requires their safe conversion to
void * and back, then for member functions there is nothing like that. However, there is a way out. Let the spectrum of processed functions that you want to bring to a single call interface in pseudocode looks like this:
function<return_type (...)> function_ptr; return_type free_function(...); class Class { return_type member_function(...); };
It seems, but not the same. Let's transform the second method:
return_type member_function_wrapper(Class *context, ...) { return context->member_function(...); }
Already better. Another iteration:
return_type free_function_wrapper(void *unused, ...) { return free_function(...); } return_type member_function_wrapper(void *context, ...) { return static_cast<Class*>(context)->member_function(...); }
Great, both functions,
free_function_wrapper and
member_function_wrapper, have the same signature. If there shouldn't be any questions from the first, then it remains for the second to figure out how to bring the class information and the pointer to the member function to its context. And this possibility is also due to the templates, which can be parameterized not only by the types and integral compilation time constants, but also by the addresses of functions and member functions. A simple abstract example:
#include <iostream> struct bar { int baz(int v) { std::cout << "bar::baz: " << v << std::endl; return v; } }; template <typename Class, int (Class::*MemberFunctionPtr)(int)> int function_wrapper(void *self, int v) { return (static_cast<Class*>(self)->*MemberFunctionPtr)(v); } int main() { typedef int (*function_ptr)(void*, int); function_ptr f = &function_wrapper<bar, &bar::baz>; bar obj; int const i = f(&obj, 1); std::cout << "result: " << i << std::endl; }
Thus, in
function, it suffices to store a pointer to the template wrapper instantiated with the necessary parameters and a pointer to the context, which in the case of the member function will be equal to the pointer to the object instance, in the context of which the function must be executed, otherwise just
NULL . No memory allocations, a trivial copy constructor and an assignment operator - in my opinion, great.
In conclusion, it remains to give the source text of the header with the implementation of the first example. I do not see its meaning line by line, the main idea is indicated. I note that the processing of a different number of function parameters is implemented using
templates with a variable number of arguments from C ++ 11, and this will require the most additional code in the case of migration to C ++ 03.
function.hpp #pragma once //#define BC_NO_EXCEPTIONS #include <utility> #include <functional> #define BC_SUBST(Arg) Arg #define BC_BIND_DISAMBIGUATE2(has_args, ...) BC_SUBST(BC_BIND_ ## has_args (__VA_ARGS__)) #define BC_BIND_DISAMBIGUATE(has_args, ...) BC_BIND_DISAMBIGUATE2(has_args, __VA_ARGS__) #define BC_HAS_ARGS_IMPL(TWO, ONE, N, ...) N #define BC_HAS_ARGS(...) BC_SUBST(BC_HAS_ARGS_IMPL(__VA_ARGS__, 2, 1, ERROR)) #define BC_BIND(...) BC_BIND_DISAMBIGUATE(BC_HAS_ARGS(__VA_ARGS__), __VA_ARGS__) #define BC_BIND_1(fp) bc::detail::bind<decltype(fp), fp>() #define BC_BIND_2(mf, ip) bc::detail::bind<decltype(mf), mf>(ip) namespace bc // bicycle { template <typename Signature> class function; namespace detail { template <typename Signature> struct function_traits; template <typename ReturnType, typename ...ArgumentTypes> struct function_traits<ReturnType (*)(ArgumentTypes...)> { //typedef ReturnType (*Signature)(ArgumentTypes...); // MS error C3522: parameter pack cannot be expanded in this context typedef function<ReturnType (ArgumentTypes...)> function_type; template <typename Signature, Signature fp> static ReturnType wrapper(void const *, ArgumentTypes&& ... args) { return (*fp)(std::forward<ArgumentTypes>(args)...); } }; template <typename ReturnType, typename Class, typename ...ArgumentTypes> struct function_traits<ReturnType (Class::*)(ArgumentTypes...)> { //typedef ReturnType (Class::*Signature)(ArgumentTypes...); // MS error C3522: parameter pack cannot be expanded in this context typedef Class * class_ptr; typedef function<ReturnType (ArgumentTypes...)> function_type; template <typename Signature, Signature mf> static ReturnType wrapper(const void *ip, ArgumentTypes&& ... args) { Class* instance = const_cast<Class*>(static_cast<Class const *>(ip)); return (instance->*mf)(std::forward<ArgumentTypes>(args)...); } }; template <typename ReturnType, typename Class, typename ...ArgumentTypes> struct function_traits<ReturnType (Class::*)(ArgumentTypes...) const> { //typedef ReturnType (Class::*Signature)(ArgumentTypes...) const; // MS error C3522: parameter pack cannot be expanded in this context typedef const Class * class_ptr; typedef function<ReturnType (ArgumentTypes...)> function_type; template <typename Signature, Signature mf> static ReturnType wrapper(void const *ip, ArgumentTypes&& ... args) { Class const *instance = static_cast<Class const *>(ip); return (instance->*mf)(std::forward<ArgumentTypes>(args)...); } }; // bind free function template <typename Signature, Signature fp> typename function_traits<Signature>::function_type bind() { typedef function_traits<Signature> traits; return typename traits::function_type(&traits::template wrapper<Signature, fp>, 0); } // bind member function template <typename Signature, Signature mf> typename function_traits<Signature>::function_type bind(typename function_traits<Signature>::class_ptr ip) { typedef function_traits<Signature> traits; return typename traits::function_type(&traits::template wrapper<Signature, mf>, ip); } } template <typename ReturnType, typename ...ArgumentTypes> class function<ReturnType (ArgumentTypes...)> { typedef ReturnType (*StaticFuncPtr)(void const*, ArgumentTypes&& ...); public: function() : func_(0), data_(0) {} function(StaticFuncPtr f, void const *d) : func_(f), data_(d) {} ReturnType operator () (ArgumentTypes... args) const { #ifndef BC_NO_EXCEPTIONS if (!func_) throw std::bad_function_call(); #endif // BC_NO_EXCEPTIONS return (*func_)(data_, std::forward<ArgumentTypes>(args)...); } explicit operator bool() const { return 0 != func_; } bool operator == (function const &other) const { return func_ == other.func_ && data_ == other.data_; } bool operator != (function const &other) const { return !(*this == other); } private: StaticFuncPtr func_; void const *data_; }; }