函数延迟绑定的C++实现
- 本文代码需要
c++17
支持(可自行修改以兼容c++11
)
概述
有时候我们会对相同的数据做不同的操作,例如:
int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int do_sth(int a, int b, const std::string& function_name) { if (function_name == "add") return add(a, b); if (function_name == "mul") return mul(a, b); }
这种做法是可行的,但是当我们还需要添加sub(a, b)
,div(a, b)
等等的多个函数时,每添加一个函数,我们都要在do_sth
中添加一个if
,很容易出错,也不符合开闭原则。
另一种实现方法是将每个操作单独封装成一个类,然后使用工厂模式创建。这种做法是符合开闭原则的,但是每添加一个函数就要添加一个类未免也太繁琐了。
理想情况下,倘若有一门语言同时结合了c++
, java
,python
,应该这样添加函数:
// 函数管理类functionmanager // @register():将函数注册到这个类中 // getfunction(): 将已注册的函数返回 class functionmanager; @register("add") // 将函数add注册到functionmanager,通过字符串"add"能够找到这个函数 int add(int a, int b) { return a + b; } @register("mul") // 将函数mul注册到functionmanager,通过字符串"mul"能够找到这个函数 int mul(int a, int b) { return a * b; } int do_sth(int a, int b, const std::string& function_name) { // 函数管理类根据function_name返回一个std::function std::function<int(int, int)> function = functionmanager.getfunction(function_name); return function(a, b); }
-
functionmanager
: 管理函数的类 -
@register("add")
: 将函数指针和函数签名(能够唯一标识该函数的字符串)添加到functionmanager
中 -
functionmanager.getfunction()
: 根据函数签名返回函数指针
很明显,当前的c++
不支持@register
,退而求其次我们使用宏进行注册,最终本文实现的效果是(在最后提供了可运行的完整程序):
// xxx.h int add(int a, int b); // xxx.cpp // 注册函数,参数为:变量名(add),函数签名("add"),函数指针(add) _register_function(add, "add", add); int add(int a, int b) { return a + b; } // main.cpp int do_sth(int a, int b, const std::string& func_sig) { // functionmanager是一个单例 auto p_function_manager = functionmanager<decltype(add)>::getinstance(); // 此处返回已注册的函数 auto func = p_function_manager->getfunction(func_sig); return }
实现
我们将管理函数的类命名为functionmanager
。仔细分析我们的需求,不难发现实际上我们需要根据字符串查找已注册的函数对象。
查找的实现
stl中已经有了能够满足我们需求的std::map<std::string, functiontype>
容器,因此查找功能已经实现了。需要注意的有:
-
functiontype
需要外部提供,而函数的返回值、参数都会改变都会影响functiontype
,我们不可能把functiontype
硬编码到程序中,这时候就需要模板。 - 由于函数指针不能够作为函数的返回值,获取函数的接口只能返回
std::function
,因此map
中存储的也应当是std::function
。 - 考虑到程序中仅有一个
functionmanager
,将functionmanager
设置为单例更方便(同时也能满足注册函数的需求)。
template <typename functiontype> class functionmanager { // 没有delete这个指针,考虑到这是demo,忽略这个问题 inline static functionmanager<functiontype>* p_function_manager = nullptr; std::map<std::string, functiontype> m_sig_func_map; public: static functionmanager<functiontype>* get_instance() { if (!p_function_manager) p_function_manager = new functionmanager<functiontype>; return p_function_manager; } // 实际上不能使用运算符[],因为当map中sig不存在时会自动创建一个<sig, empty>对象,demo中忽略这个问题 functiontype get_function(const std::string& sig) { return m_sig_func_map[sig]; } };
注册的实现
注册实际上就是将函数签名(字符串)和函数(std::function
对象)添加到functionmanager::m_sig_func_map
中,只需要添加一个接口:
// 也可以使用insert,二者间存在一点区别 void register_function(const std::string& sig, functiontype function) { m_sig_func_map[sig] = function; }
尝试使用
到这里我们已经可以使用functionmanager
了:
int add(int a, int b) { return a + b; } void use() { std::function<int(int, int)> a(add); functionmanager<std::function<decltype(add)>>::get_instance()->register_function("add", a); auto another_add = functionmanager<std::function<decltype(add)>>::get_instance()->get_function("add"); std::cout << another_add(1, 3) << std::endl; }
我们看到functionmanager
的使用其实是很不方便的:
- 注册时需要提供对应函数的
std::function
对象,实际上我们在使用的时候只希望提供函数的指针 - 注册和获取函数时都需要需要获取单例
这些冗杂的代码可以交给单独的接口进行封装:
template <typename functionptr> void register_function(const std::string& function_sig, functionptr function_ptr) { auto function_obj = static_cast<std::function<functionptr>>(function_ptr); auto p_function_manager = functionmanager<decltype(function_obj)>::get_instance(); p_function_manager->register_function(function_sig, function_obj); } template <typename functiontype> functiontype get_function(const std::string& function_sig) { auto p_function_manager = functionmanager<functiontype>::get_instance(); return p_function_manager->get_function(function_sig); } int main(int argc, char* argv[]) { register_function<decltype(add)>("add", add); // 只提供指针 auto another_add = get_function<std::function<int(int, int)>>("add"); std::cout << another_add(1, 4) << std::endl; return 0; }
相比之下使用起来方便多了,实际上到这里functionmanager
的实现就已经完成了,但是还要让functionmanager
更好用,以及让functionmanager
适用于更多类型的函数,下面才是文章的重点
注册
现有的functionmanager
如果直接使用还会存在一个问题:需要由用户保证在get_function
前register_function
。在demo中这并不是大问题,但是放在大型项目中,get_function
在多个文件中被多次调用,需要由用户保证register_function
在所有get_function
前执行,这实在是太危险了。稳妥一点的方法是:
void register_all_function() { auto p_function_manager = /* get singleton instance */ ; p_function_manager->register_function(sig_1, func_1); p_function_manager->register_function(sig_2, func_2); // ... } int main { register_all_function(); }
在main
最开始的时候进行注册是最安全的做法,但是每添加一个函数,用户就要在register_all_function
中进行注册,这也违背了开闭原则。
因此我们有了新的需求:main函数执行前完成对象的注册。有能够在main
前执行一段函数:静态成员变量和全局变量,需要注意的是二者均需要在cpp
文件中定义,不能够在头文件中。通过register_function
为全局变量赋值能够在main
前执行register_function
,因此我们还需要为register_function
添加一个返回值:
template <typename functionptr> bool register_function(const std::string& function_sig, functionptr function_ptr) { // ... return true; }
用全局变量注册函数
通过定义全局变量能够在main
执行前将需要的函数注册至functionmanager
。为了在使用更方便、代码可读性更高,functionmanager
提供了一个宏_register_function
,用于封装注册函数。其原理如下:
#define _register_function(functionsig, functionptr) \ bool b = register_function<decltype(functionptr)>(functionsig, functionptr);
这样带来了新的问题:在实际使用时,一个.cpp
文件内通常会有多个类型相同的函数的实现,_register_function
将被调用多次:
_register_function("add", add); int add(int a, int b) { return a + b; } _register_function("mul", mul); // 编译错误,重复定义的变量b int mul(int a, int b) { return a * b; }
多次调用_register_function
会导致全局变量b
被重复定义。因此需要用户手动提供不重复的变量名(variablename
)以防止编译错误,最终_register_function
的实现如下:
#define _register_function(variablename, functionsig, functionptr) \ bool bool##variablename = register_function<decltype(functionptr)>(functionsig, functionptr); // xxx.h int add(int, int); // xxx.cpp _register_function(add, "add", add); int add(int a, int b) { return a + b; }
functionmanager适配的函数
普通函数
- 普通函数
float add(float a, float b) { return a + b; } _register_function(add, "add", add); auto new_add = get_function<decltype(add)>("add");
- 命名空间内的函数
namespace functionmanagertest { float add(float a, float b) { return a + b; } } _register_function(add, "add", functionmanagertest::add); auto new_add = get_function<std::function<decltype(functionmanagertest::add)>>("add");
- 模板函数
template<typename t> t addt(t a, t b) { return a + b; } _register_function(add, "add", addt<int>); auto new_add = get_function<std::function<decltype(addt<int>)>>("add");
类内函数
- 静态函数
class real { public: static float add(float a, float b) { return a + b; } }; _register_function(add, "add", real::add); auto new_add = get_function<std::function<decltype(real::add)>>("add");
- 模板类的静态函数
template<typename t> class add { public: static float add(t a, t b) { return a + b; } }; _register_function(add, "add", add<int>::add); auto new_add = get_function<std::function<decltype(add<int>::add)>>("add");
适配成员函数
现在的functionmanager
能够支持的函数少了很重要的一类:成员函数。因为成员函数在被调用时会有一个this
指针作为隐式参数,显然直接通过&real::add
是无法获得this
指针的。这意味着我们需要添加新的接口。
template<typename functionptr, typename objectptr> bool register_member_function(functionptr func_ptr, objectptr obj_ptr) { return true; }
回看需求,我们希望能够在functionmanager
中获取到函数后,能够直接调用;同时functionmanager
中管理的也只是能够直接调用的std::function
对象,并未区分成员函数或者非成员函数。现在问题简化为如何为某一个函数提供一个默认的参数(对象指针),提供后,我们就能像调用普通函数一样调用成员函数了。
我们知道std::bind
能够将函数与参数绑定,返回一个callable
对象;;结合std::placeholder
还能够在调用返回的callable
时提供参数:
class real { public: int add(int a, int b) { return a + b; } } real real; std::function<int(int,int)> binded_add = std::bind(&real::add, &real, std::placeholders::_1, std::placeholders::_2); std::cout << binded_add(2, 1) << std::endl;
现在如何提供默认参数的问题解决了,但是另一个问题又出现了:代码中的std::function<int(int,int)>
是硬编码进去的,肯定不能实装,我们需要一种能够自动填充std::function<>
内模板参数的方法。在网上找了大半天后,:
template <typename ret, typename struct, typename ...args, typename objectptr> bool register_memeber_function(const std::string& sig, ret(struct::* func_ptr)(args...) const, objectptr obj_ptr) { std::function<ret(args...)> func = std::bind(func_ptr, obj_ptr); auto p_funciton_manager = functionmanager<decltype(func)>::get_instance(); p_function_manager->register_function(sig, func); return true; } class real { public: int add(int a, int b) { return a + b; } } real real; bool b = register_member_function("add", &real::add, &real);
将&real::add
作为参数传入后能够自动推导出ret, struct
以及可变参数args
;由于我们会绑定对象指针,因此我们只需要返回值ret
,参数args
作为std::function
的模板参数。这样一来std::function
的模板参数问题终于解决了。
然而现在的代码是无法通过编译的,因为std::bind
中没有添加正确数量的std::placeholder
。这个问题的解决需要用到一点元编程,*上有人:
// https://*.com/questions/26129933/bind-to-function-with-an-unknown-number-of-arguments-in-c template<int n> struct my_placeholder { static my_placeholder ph; }; template<int n> my_placeholder<n> my_placeholder<n>::ph; namespace std { template<int n> struct is_placeholder<::my_placeholder<n>> : std::integral_constant<int, n> { }; } template<class r, class t, class...types, class u, int... indices> std::function<r (types...)> bind_first(std::function<r (t, types...)> f, u val, std::integer_sequence<int, indices...> /*seq*/) { return std::bind(f, val, my_placeholder<indices+1>::ph...); } template<class r, class t, class...types, class u> std::function<r (types...)> bind_first(std::function<r (t, types...)> f, u val) { return bind_first(f, val, std::make_integer_sequence<int, sizeof...(types)>()); }
这里的核心思想是在模板中传入长度与参数个数相同的整数序列,并且为每个序列中的整数添加一个placeholder
。其实并不需要自己定义一个placeholder
,因为std::placeholder
的实现是类似的:
// placeholder arguments namespace placeholders { _inline_var constexpr _ph<1> _1{}; _inline_var constexpr _ph<2> _2{}; } // namespace placeholders
结合已有实现以及std::placeholder
的解决方案如下:
template <typename ret, typename struct, typename ...args, typename objectptr, int... indices> std::function<ret(args...)> erase_class_info(ret(struct::* func_ptr)(args...), objectptr obj_ptr, std::integer_sequence<int, indices...>) { std::function<ret(args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_ph<indices + 1>{}...); return erased_function; } template <typename ret, typename struct, typename ...args, typename objectptr> bool register_memeber_function(const std::string& sig, ret(struct::* func_ptr)(args...), objectptr obj_ptr) { std::function<ret(args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(args)>()); auto p_funciton_manager = functionmanager<decltype(erased_func)>::get_instance(); p_funciton_manager->register_function(sig, erased_func); return true; }
另外,成员函数添加const
以后的函数类型是不同的,简单地添加两个类似接口可以解决这个问题:
template <typename ret, typename struct, typename ...args, typename objectptr, int... indices> std::function<ret(args...)> erase_class_info(ret(struct::* func_ptr)(args...) const, objectptr obj_ptr, std::integer_sequence<int, indices...>) { std::function<ret(args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_ph<indices + 1>{}...); return erased_function; } template <typename ret, typename struct, typename ...args, typename objectptr> bool register_memeber_function(const std::string& sig, ret(struct::* func_ptr)(args...) const, objectptr obj_ptr) { std::function<ret(args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(args)>()); auto p_funciton_manager = functionmanager<decltype(erased_func)>::get_instance(); p_funciton_manager->register_function(sig, erased_func); return true; }
现在已经支持成员函数的注册了:
class real { public: int add(int a, int b) const { return a + b; } int sub(int a, int b) { return a - b; } }; real real; bool b1 = register_memeber_function("real", &real::add, &real); bool b2 = register_memeber_function("real", &real::sub, &real); auto f1 = get_function<std::function<int(int, int)>>("real"); auto f2 = get_function<std::function<int(int, int)>>("real"); std::cout << f1(2, 1) << std::endl; std::cout << f2(2, 1) << std::endl;
至此functionmanager
实现完成
补充
更简单的接口
前面提到了使用静态成员变量也能够实现注册功能,用静态成员变量注册函数
可以进一步减少用户需要写的代码,functionmanager
希望不需要用户提供变量名。参考boost宏boost_class_export
,其利用模板类和静态成员变量实现了类的注册,并且不需要用户提供变量名,其原理如下:
namespace boost::archive::detail::extra_detail { template<> struct init_guid<classtoregister> { static guid_initializer<classtoregister> const& g; //静态成员g } static guid_initializer<classtoregister> const& g = register_function(); // 定义g,同时注册类 }
boost_class_export
还是不能满足需求,因为boost_class_export
用于注册类,倘若用于注册多个相同类型的函数会导致静态成员g重复定义,而实际注册函数时同一类型的函数往往会被注册多次。如:
int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } _boost_like_register("add", add); _boost_like_register("mul", mul); // 静态成员g重复定义
functionmanager
通过命名空间解决重复定义这一问题,宏_easy_register_function
实现如下:
#define _easy_register_function(functionsig, functionptr) \ namespace vicentchenspace { \ namespace dummy { \ namespace functionptr { \ struct dummy { \ static bool const& b; \ }; \ } \ } \ } \ bool const& vicentchenspace::dummy::functionptr::dummy::b = register_function<decltype(::functionptr)>(functionsig, ::functionptr); \
虽然_easy_register_function
使用起来更方便,但有以下问题:
- 需要
c++17
支持 - 不支持注册带有模板的函数
- 命名空间相关问题
可运行demo
#include <functional> #include <map> #include <iostream> #define _register_function(variablename, functionsig, functionptr) \ bool bool##variablename = register_function<decltype(functionptr)>(functionsig, functionptr); template <typename functiontype> class functionmanager { // 没有delete这个指针,考虑到这是demo,忽略这个问题 inline static functionmanager<functiontype>* p_function_manager = nullptr; std::map<std::string, functiontype> m_sig_func_map; public: static functionmanager<functiontype>* get_instance() { if (!p_function_manager) p_function_manager = new functionmanager<functiontype>; return p_function_manager; } // 也可以使用insert,二者间存在一点区别 void register_function(const std::string& sig, functiontype function) { m_sig_func_map[sig] = function; } // 实际上不能使用运算符[],因为当map中sig不存在时会自动创建一个<sig, empty>对象,demo中忽略这个问题 functiontype get_function(const std::string& sig) { return m_sig_func_map[sig]; } }; template <typename functionptr> bool register_function(const std::string& function_sig, functionptr function_ptr) { auto function_obj = static_cast<std::function<functionptr>>(function_ptr); auto p_function_manager = functionmanager<decltype(function_obj)>::get_instance(); p_function_manager->register_function(function_sig, function_obj); return true; } template <typename functiontype> functiontype get_function(const std::string& function_sig) { auto p_function_manager = functionmanager<functiontype>::get_instance(); return p_function_manager->get_function(function_sig); } template <typename ret, typename struct, typename ...args, typename objectptr, int... indices> std::function<ret(args...)> erase_class_info(ret(struct::* func_ptr)(args...), objectptr obj_ptr, std::integer_sequence<int, indices...>) { std::function<ret(args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_ph<indices + 1>{}...); return erased_function; } template <typename ret, typename struct, typename ...args, typename objectptr> bool register_memeber_function(const std::string& sig, ret(struct::* func_ptr)(args...), objectptr obj_ptr) { std::function<ret(args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(args)>()); auto p_funciton_manager = functionmanager<decltype(erased_func)>::get_instance(); p_funciton_manager->register_function(sig, erased_func); return true; } template <typename ret, typename struct, typename ...args, typename objectptr, int... indices> std::function<ret(args...)> erase_class_info(ret(struct::* func_ptr)(args...) const, objectptr obj_ptr, std::integer_sequence<int, indices...>) { std::function<ret(args...)> erased_function = std::bind(func_ptr, obj_ptr, std::_ph<indices + 1>{}...); return erased_function; } template <typename ret, typename struct, typename ...args, typename objectptr> bool register_memeber_function(const std::string& sig, ret(struct::* func_ptr)(args...) const, objectptr obj_ptr) { std::function<ret(args...)> erased_func = erase_class_info(func_ptr, obj_ptr, std::make_integer_sequence<int, sizeof...(args)>()); auto p_funciton_manager = functionmanager<decltype(erased_func)>::get_instance(); p_funciton_manager->register_function(sig, erased_func); return true; } // ----- 普通函数注册 ----- // // 普通函数 int add(int a, int b) { return a + b; } _register_function(add, "add", add); int mul(int a, int b) { return a * b; } _register_function(mul, "mul", mul); // 命名空间内函数 namespace vicentspace { int add(int a, int b) { return a + b; } } _register_function(namespace_add, "namespace_add", vicentspace::add); // 模板函数 template<typename t> t addt(t a, t b) { return a + b; } _register_function(template_add, "template_add", addt<int>); // ----- 类内函数注册 ----- // class real { public: int add(int a, int b) const { return a + b; } int sub(int a, int b) { return a - b; } static int mul(int a, int b) { return a * b; } }; real real; template <typename t> class realt { public: t addt(t a, t b) const { return a + b; } t subt(t a, t b) { return a - b; } static t mult(t a, t b) { return a * b; } }; realt<int> real_t; // 静态函数 _register_function(static_mul, "static_mul", real::mul); // 静态模板函数 _register_function(static_template_mul, "static_template_mul", realt<int>::mult); // 成员函数 bool b1 = register_memeber_function("real_add", &real::add, &real); bool b2 = register_memeber_function("real_sub", &real::sub, &real); // 模板成员 bool b3 = register_memeber_function("realt_add", &realt<int>::addt, &real_t); bool b4 = register_memeber_function("realt_sub", &realt<int>::subt, &real_t); int main(int argc, char* argv[]) { // 普通函数 auto normal_add = get_function<std::function<decltype(add)>>("add"); auto normal_mul = get_function<std::function<decltype(add)>>("mul"); std::cout << "normal add 1 + 2 = " << normal_add(1, 2) << std::endl; std::cout << "normal mul 1 * 2 = " << normal_mul(1, 2) << std::endl; // 命名空间内函数 auto namespace_add = get_function<std::function<decltype(vicentspace::add)>>("namespace_add"); std::cout << "namespace add 1 + 2 = " << namespace_add(1, 2) << std::endl; // 模板函数 auto template_add = get_function<std::function<int(int, int)>>("template_add"); std::cout << "template add 1 + 2 = " << template_add(1, 2) << std::endl; // 静态函数 auto static_mul = get_function<std::function<int(int, int)>>("static_mul"); std::cout << "static mul 1 * 2 = " << static_mul(1, 2) << std::endl; // 静态模板函数 auto static_template_mul = get_function<std::function<int(int, int)>>("static_template_mul"); std::cout << "static template mul 1 * 2 = " << static_template_mul(1, 2) << std::endl; // 成员函数 auto real_add = get_function<std::function<int(int, int)>>("real_add"); auto real_sub = get_function<std::function<int(int, int)>>("real_sub"); std::cout << "member add 2 + 1 = " << real_add(2, 1) << std::endl; std::cout << "const member sub 2 - 1 = " << real_sub(2, 1) << std::endl; // 模板成员 auto real_t_add = get_function<std::function<int(int, int)>>("realt_add"); auto real_t_sub = get_function<std::function<int(int, int)>>("real_sub"); std::cout << "template member add 2 + 1 = " << real_t_add(2, 1) << std::endl; std::cout << "template const member sub 2 - 1 = " << real_t_sub(2, 1) << std::endl; return 0; }
参考
上一篇: Linux服务器打包项目后,fastdfs配置文件找不到
下一篇: 读书人就是比不读书的人聪明