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

如何获取非静态数据成员指针的字符串表示?

问题:为指向A类不同成员的B类实例创建以std::string为键的唯一容器

我需要实现一个容器,存储B类的唯一实例——每个实例对应A类的不同数据成员,且容器的键必须是std::string类型。

之前尝试把成员指针转成整数再转成字符串当键,但非静态成员指针无法直接转换为可靠的整数表示;业务场景中A类实例需要用户输入,没法通过静态A实例获取成员地址;试过std::type_info,但.name()结果不具备跨编译器/版本的一致性,且必须用std::string作为键,不能存储std::type_index。

以下是我编写的无法编译的代码:

#include <iostream>
#include <unordered_map>
#include <cassert>
#include <string>

struct A
{
    int m_foo;
    int m_bar;
};

struct BBase
{
    BBase(std::uintptr_t ptr)
    :
        m_ptr(ptr)
    {}
    
    std::uintptr_t m_ptr;
};

template<auto M>
struct B : public BBase
{
    using AType = std::remove_cvref_t<decltype(std::declval<A>().*M)>;
    
    B(const AType a)
    :
        BBase(reinterpret_cast<std::uintptr_t>(M)),
        m_a(a)
    {
    }
    
    void Assign(A& a)
    {
        a.*M = m_a;
    }
    
    AType m_a;
};

using AFoo = B<&A::m_foo>;
using ABar = B<&A::m_bar>;

int main()
{
    std::unordered_map<std::string, BBase*> map;
    
    A a{.m_foo = 1, .m_bar = 4};
    
    auto aFoo = new AFoo(2);
    auto aBar = new ABar(2);
    
    map[std::to_string(aFoo->m_ptr)] = aFoo;
    map[std::to_string(aBar->m_ptr)] = aBar;
    
    assert(map.size() == 2);
    
    aFoo->Assign(a);
    
    std::cout << a.m_foo << std::endl;
    
    return 0;
}

解决方案:基于模板特化生成唯一字符串键

方法1:自动生成唯一哈希字符串键

利用每个模板特化的静态变量唯一地址,生成稳定的字符串键,避免成员指针转换的未定义行为:

#include <iostream>
#include <unordered_map>
#include <cassert>
#include <string>
#include <type_traits>

struct A
{
    int m_foo;
    int m_bar;
};

struct BBase
{
    virtual ~BBase() = default; // 基类添加虚析构避免内存泄漏
    virtual const std::string& GetKey() const = 0;
    virtual void Assign(A& a) = 0;
};

// 为每个成员指针特化生成唯一键的辅助模板
template<auto M>
struct MemberKeyGenerator {
    static std::string Get() {
        // 利用静态变量的唯一地址生成哈希字符串
        static const std::string key = std::to_string(reinterpret_cast<std::uintptr_t>(&s_tag));
        return key;
    }
private:
    static inline const char s_tag{}; // 每个特化拥有独立的静态变量
};

template<auto M>
struct B : public BBase
{
    using AType = std::remove_cvref_t<decltype(std::declval<A>().*M)>;
    
    B(const AType a) : m_a(a) {}
    
    void Assign(A& a) override {
        a.*M = m_a;
    }

    const std::string& GetKey() const override {
        return MemberKeyGenerator<M>::Get();
    }
    
    AType m_a;
};

using AFoo = B<&A::m_foo>;
using ABar = B<&A::m_bar>;

int main()
{
    std::unordered_map<std::string, BBase*> map;
    
    A a{.m_foo = 1, .m_bar = 4};
    
    auto aFoo = new AFoo(2);
    auto aBar = new ABar(2);
    
    map[aFoo->GetKey()] = aFoo;
    map[aBar->GetKey()] = aBar;
    
    assert(map.size() == 2);
    
    aFoo->Assign(a);
    
    std::cout << a.m_foo << std::endl;

    // 清理内存
    delete aFoo;
    delete aBar;
    
    return 0;
}

方法2:手动指定可读字符串键

如果需要键具备可读性,可以在模板实例化时手动传入字符串常量:

// 修改B模板,新增字符串模板参数
template<auto M, const char* Key>
struct B : public BBase
{
    using AType = std::remove_cvref_t<decltype(std::declval<A>().*M)>;
    
    B(const AType a) : m_a(a) {}
    
    void Assign(A& a) override {
        a.*M = m_a;
    }

    const std::string& GetKey() const override {
        static const std::string key = Key;
        return key;
    }
    
    AType m_a;
};

// 定义全局字符串常量
constexpr const char FooKey[] = "m_foo";
constexpr const char BarKey[] = "m_bar";

// 实例化时指定键
using AFoo = B<&A::m_foo, FooKey>;
using ABar = B<&A::m_bar, BarKey>;

关键说明

  1. 原代码问题:reinterpret_cast<std::uintptr_t>(M)对非静态成员指针属于未定义行为——成员指针的大小可能超过uintptr_t,且无标准转换规则。
  2. 方法1通过模板特化的静态变量地址生成键,保证每个成员对应唯一、稳定的字符串键,编译期确定,运行时无额外开销。
  3. 基类补充虚析构和纯虚函数,符合多态设计规范,避免内存泄漏。

内容的提问来源于stack exchange,提问作者D.G. Redd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:07:18