成员指针的Type erasure实现:UniversalType选型及uintptr_t适用性咨询
成员指针的Type erasure实现:UniversalType选型及uintptr_t适用性咨询
嘿,这个问题我太有发言权了——之前在嵌入式项目里刚搞定过类似的成员指针类型擦除需求,咱们一步步把它说透:
先给结论:uintptr_t完全不能用来存非静态成员指针
别踩这个坑!非静态成员指针(比如int A::*、bool A::*这种)和普通对象指针完全是两码事:
- 它的底层表示不一定是单一的地址值:在多继承、虚继承的场景下,编译器会给成员指针附加偏移量信息,有些平台上它的大小甚至会是
uintptr_t的2倍(比如64位平台上占16字节,而uintptr_t是8字节)。 - 标准C++明确禁止非静态成员指针和
uintptr_t之间的强制转换,这属于未定义行为——今天在你的开发板上可能碰巧能用,换个编译器、换个CPU架构直接就崩溃,完全没有移植性可言。
适合嵌入式无RTTI场景的UniversalType方案
既然不能用大三方库、不能靠RTTI,咱们得用编译期分发+对齐存储+函数指针的组合来实现类型擦除,这完全符合你说的“编译期 dispatch 可行”的要求,而且效率比手动写函数指针高得多。
核心思路:用对齐存储存原始成员指针,配合编译期生成的访问函数
直接上可落地的极简实现思路,完全贴合嵌入式场景:
- 先定义一个通用的存储结构体,用来容纳任何类型的成员指针(要预留足够大的空间和正确的对齐):
// 预留足够大的空间:绝大多数平台上非静态成员指针最大是2倍普通指针的大小 constexpr size_t MaxMemberPtrSize = sizeof(void*) * 2; // 对齐要求和普通指针一致即可 constexpr size_t MaxMemberPtrAlign = alignof(void*); struct UniversalMemberPtr { alignas(MaxMemberPtrAlign) char storage[MaxMemberPtrSize]; // 存两个函数指针:分别负责读取和修改成员,实现类型擦除后的安全访问 void (*get)(const void* obj, void* out_val); void (*set)(void* obj, const void* in_val); }; - 写一个模板函数,编译期为不同类型的成员指针生成对应的
UniversalMemberPtr:template <typename MemberType, typename Class> UniversalMemberPtr make_universal_member(MemberType Class::*member) { UniversalMemberPtr ump{}; // 把成员指针原封不动拷贝到存储区,不管它底层是什么结构 std::memcpy(ump.storage, &member, sizeof(member)); // 编译期生成读取成员的函数 ump.get = [](const void* obj, void* out_val) { const Class* class_obj = static_cast<const Class*>(obj); // 从存储区还原成员指针 MemberType Class::*target_member; std::memcpy(&target_member, ump.storage, sizeof(target_member)); // 读取成员值 *static_cast<MemberType*>(out_val) = class_obj->*target_member; }; // 编译期生成修改成员的函数 ump.set = [](void* obj, const void* in_val) { Class* class_obj = static_cast<Class*>(obj); MemberType Class::*target_member; std::memcpy(&target_member, ump.storage, sizeof(target_member)); class_obj->*target_member = *static_cast<const MemberType*>(in_val); }; return ump; } - 实际使用的例子,完全贴合你给出的
struct A场景:struct A { int a; bool b; std::string c; }; int main() { A my_obj{42, true, "hello embedded"}; // 把不同类型的成员指针统一转成UniversalMemberPtr auto ptr_a = make_universal_member(&A::a); auto ptr_b = make_universal_member(&A::b); auto ptr_c = make_universal_member(&A::c); // 读取成员值 int val_a; ptr_a.get(&my_obj, &val_a); // val_a 现在是42 // 修改成员值 bool new_b = false; ptr_b.set(&my_obj, &new_b); // my_obj.b 现在是false // 字符串成员也能正常处理 std::string new_c = "type erasure works!"; ptr_c.set(&my_obj, &new_c); }
这个方案的优势完全匹配你的需求
- 无RTTI、无三方库依赖:全程都是标准C++基础语法,完全适配嵌入式环境
- 效率拉满:访问函数是编译期生成的,没有额外运行时开销,比你现在用的手动函数指针分发更简洁且不易出错
- 100%可移植:不管编译器怎么实现成员指针,只要存储区足够大,就能原封不动保存所有信息,不会丢失偏移量这类关键数据
- 编译期安全:在创建
UniversalMemberPtr的时候是完全类型安全的,只有在擦除后才通过函数指针保证操作正确性
补充:如果是静态成员指针呢?
如果你的需求里包含静态成员(比如static int A::s),那静态成员指针本质就是普通对象指针,这时候用uintptr_t是没问题的——但你的问题里都是非静态成员,所以这条可以忽略。
内容来源于stack exchange
相关产品推荐
相关产品推荐

