You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

成员指针的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 可行”的要求,而且效率比手动写函数指针高得多。

核心思路:用对齐存储存原始成员指针,配合编译期生成的访问函数

直接上可落地的极简实现思路,完全贴合嵌入式场景:

  1. 先定义一个通用的存储结构体,用来容纳任何类型的成员指针(要预留足够大的空间和正确的对齐):
    // 预留足够大的空间:绝大多数平台上非静态成员指针最大是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);
    };
    
  2. 写一个模板函数,编译期为不同类型的成员指针生成对应的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;
    }
    
  3. 实际使用的例子,完全贴合你给出的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:12:59