资源受限MCU下无标准库无堆分配的C++成员函数包装器实现
无reinterpret_cast的成员函数指针包装器实现(嵌入式场景适用)
该实现完全满足你的需求:仅已知返回值和参数类型即可定义包装器,无需提前感知成员函数所属类;无任何堆内存分配,所有存储内建在包装器结构体中;不依赖任何C++标准库组件;完全规避未定义行为的reinterpret_cast hack。
实现原理
采用编译期生成类型擦除跳转函数(Trampoline)的方案:
- 用
void*存储任意类的对象实例指针 - 用对齐的字节缓冲区存储任意类型的成员函数指针
- 针对每个传入的类类型
T,编译期生成对应的静态跳转函数,负责从缓冲区取出成员函数指针完成类型安全的调用
完整可运行代码
// 无C++标准库依赖,无堆分配 #include <cstdio> // 仅用于测试打印,实际项目可移除 // 编译器无内置memcpy时可以用这个简易实现替代,无需标准库 /* void* simple_memcpy(void* dest, const void* src, unsigned int n) { char* d = static_cast<char*>(dest); const char* s = static_cast<const char*>(src); for (unsigned int i = 0; i < n; i++) d[i] = s[i]; return dest; } */ template<typename Ret, typename... Args> struct member_cb { member_cb() : obj(nullptr), trampoline(nullptr) {} template<typename T> void set_cb(T* obj_, Ret (T::*func)(Args...)) { obj = static_cast<void*>(obj_); // 校验成员函数指针大小、对齐是否符合当前缓冲区配置 static_assert(sizeof(func) <= sizeof(func_buf), "成员函数指针大小超出缓冲区限制"); static_assert(alignof(decltype(func)) <= alignof(func_buf), "成员函数指针对齐要求超出缓冲区支持"); // 嵌入式编译器普遍支持__builtin_memcpy,无需引入标准库,也可替换为上面的simple_memcpy __builtin_memcpy(func_buf, &func, sizeof(func)); trampoline = &trampoline_impl<T>; } Ret operator()(Args... args) { return trampoline(obj, func_buf, args...); } private: void* obj; // 16字节对齐缓冲区,足够覆盖绝大多数嵌入式平台(包括ARM Cortex-M多继承场景)的成员函数指针需求 // 可根据实际使用平台调整大小 alignas(alignof(void (member_cb::*)())) char func_buf[16]; using TrampolineFunc = Ret(*)(void*, const char*, Args...); TrampolineFunc trampoline; // 针对每个类类型T在编译期生成专属跳转实现 template<typename T> static Ret trampoline_impl(void* obj_ptr, const char* func_buf_ptr, Args... args) { Ret (T::*func)(Args...); __builtin_memcpy(&func, func_buf_ptr, sizeof(func)); T* instance = static_cast<T*>(obj_ptr); return (instance->*func)(args...); } }; // 测试用例 struct Bar{ void someFunc(int n2){ printf("someFunc called with parameter: %d\n",n2); } }; int main() { member_cb<void, int> foo; Bar bar; foo.set_cb(&bar, &Bar::someFunc); foo(42); return 0; }
实现说明
- 运行效率极高,仅多一次函数跳转开销,和你最初的reinterpret_cast版本性能几乎一致
- 所有类型转换都是C++标准允许的静态转换,无任何未定义行为,兼容性远优于hack实现
- 缓冲区大小可根据实际使用平台灵活调整,32位ARM平台8字节即可覆盖绝大多数场景,16字节可覆盖所有多继承、虚继承场景
内容的提问来源于stack exchange,提问作者jochen
相关产品推荐
相关产品推荐

