如何获取非静态数据成员指针的字符串表示?
问题:为指向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>;
关键说明
- 原代码问题:
reinterpret_cast<std::uintptr_t>(M)对非静态成员指针属于未定义行为——成员指针的大小可能超过uintptr_t,且无标准转换规则。 - 方法1通过模板特化的静态变量地址生成键,保证每个成员对应唯一、稳定的字符串键,编译期确定,运行时无额外开销。
- 基类补充虚析构和纯虚函数,符合多态设计规范,避免内存泄漏。
内容的提问来源于stack exchange,提问作者D.G. Redd
相关产品推荐
相关产品推荐

