如何利用模板元编程调用结构体的多个成员函数
实现结构体成员函数的自动遍历调用(模板元编程方案)
C++本身没有原生反射能力,无法自动枚举结构体的所有成员函数,但可以通过模板元编程结合显式标记机制实现无需手动维护函数列表的遍历调用。以下是两种可行方案:
方案一:编译期纯模板遍历(无运行时容器)
这个方案完全在编译期生成调用代码,没有运行时开销,新增成员函数只需少量修改。
1. 定义辅助模板
#include <tuple> #include <utility> #include <cstdio> // 类型列表模板:用于存储成员函数指针类型 template<typename... Ts> struct TypeList {}; // 递归遍历模板(带参数匹配) template<typename List, typename Obj, typename... ParamTuples> struct CallMemberFuncs; // 递归终止条件:空列表 template<typename Obj> struct CallMemberFuncs<TypeList<>, Obj> { static void call(Obj&) {} }; // 递归展开逻辑:处理单个成员函数+参数,再递归处理剩余项 template<typename Func, typename... Rest, typename Obj, typename FirstParams, typename... RemainingParams> struct CallMemberFuncs<TypeList<Func, Rest...>, Obj, FirstParams, RemainingParams...> { static void call(Obj& obj) { // 展开参数tuple并调用成员函数 std::apply([&obj](auto&&... args) { (obj.*static_cast<Func>(nullptr))(std::forward<decltype(args)>(args)...); }, FirstParams{}); // 递归处理剩余函数 CallMemberFuncs<TypeList<Rest...>, Obj, RemainingParams...>::call(obj); } }; // 简化调用的辅助函数 template<typename List, typename Obj, typename... ParamTuples> void call_all(Obj& obj) { CallMemberFuncs<List, Obj, ParamTuples...>::call(obj); }
2. 定义结构体与函数列表
struct MyStruct { float a() { printf("调用a()\n"); return 3.14f; } void b(int x) { printf("调用b(%d)\n", x); } char c(float x, int y) { printf("调用c(%.1f, %d)\n", x, y); return 'a'; } }; // 用宏简化函数列表维护(新增函数只需加一行ADD_MEMBER_FUNC) #define BEGIN_FUNC_LIST(CLASS) using CLASS##FuncList = TypeList< #define ADD_MEMBER_FUNC(CLASS, FUNC) decltype(&CLASS::FUNC), #define END_FUNC_LIST >; BEGIN_FUNC_LIST(MyStruct) ADD_MEMBER_FUNC(MyStruct, a) ADD_MEMBER_FUNC(MyStruct, b) ADD_MEMBER_FUNC(MyStruct, c) END_FUNC_LIST
3. 调用遍历逻辑
int main() { MyStruct s; // 调用所有成员函数,每个函数对应一组参数tuple call_all< MyStructFuncList, MyStruct, std::tuple<>(), // a()的无参 std::tuple<int>(42), // b(int)的参数 std::tuple<float, int>(1.5f, 10) // c(float, int)的参数 >(s); return 0; }
方案二:运行时动态收集(结合std::variant)
如果需要运行时动态添加/移除函数,可以用这个方案,新增函数只需加一行宏注册。
1. 定义辅助工具
#include <vector> #include <functional> #include <variant> #include <cstdio> // 定义支持所有成员函数签名的变体类型 using MemberFuncVar = std::variant< std::function<float()>, std::function<void(int)>, std::function<char(float, int)> >; // 函数收集器模板 template<typename T> struct FuncCollector { static std::vector<MemberFuncVar> funcs; }; template<typename T> std::vector<MemberFuncVar> FuncCollector<T>::funcs; // 注册宏:绑定对象和参数,自动加入收集器 #define REGISTER_FUNC(CLASS, FUNC, ...) \ struct Register##CLASS##FUNC { \ Register##CLASS##FUNC(CLASS& obj) { \ FuncCollector<CLASS>::funcs.emplace_back( \ std::bind(&CLASS::FUNC, std::ref(obj), __VA_ARGS__) \ ); \ } \ };
2. 修改结构体并注册函数
struct MyStruct { float a() { printf("调用a()\n"); return 3.14f; } void b(int x) { printf("调用b(%d)\n", x); } char c(float x, int y) { printf("调用c(%.1f, %d)\n", x, y); return 'a'; } // 注册函数(绑定默认参数,新增函数只需加一行) REGISTER_FUNC(MyStruct, a) REGISTER_FUNC(MyStruct, b, 42) REGISTER_FUNC(MyStruct, c, 1.5f, 10) // 构造时完成注册 MyStruct() { RegisterMyStructA(*this); RegisterMyStructB(*this); RegisterMyStructC(*this); } };
3. 遍历调用
int main() { MyStruct s; // 遍历所有注册的函数 for (auto& var : FuncCollector<MyStruct>::funcs) { std::visit([](auto&& func) { func(); }, var); } return 0; }
关键说明
- 两种方案都避免了手动维护函数vector:编译期方案靠类型列表+宏,运行时方案靠注册宏;
- 编译期方案无运行时开销,但参数必须在编译期确定;运行时方案更灵活,但有轻微的运行时开销;
- 由于C++无原生反射,必须通过显式标记(宏或类型列表)告诉编译器哪些函数需要参与遍历。
内容的提问来源于stack exchange,提问作者Rogmier
相关产品推荐
相关产品推荐

