如何在无额外字段的Property类中获取外层Holder对象指针?
问题:无额外字段前提下,从Property访问外层Holder对象
需求背景
- 现有
Holder和Property两个类,Holder包含Property类型的字段 - 需要从
Property实例访问它所属的Holder对象,核心原理是:Property的地址 =Holder地址 + 字段偏移量 - 硬性限制:
Property不能加任何额外字段(比如不能在构造时传入Holder并保存为成员变量) - 补充说明:
- 该技巧仅适用于标准布局类型
- 这么做的核心目的是最小化
Holder的内存占用,理想状态是sizeof(Property)为0字节(虽然C++标准不允许)
最初尝试的问题
一开始想通过模板参数传递成员指针或偏移量,比如:
Property<&Holder::prop> prop;
或者
Property<offsetof(Holder, prop)> prop;
但问题是,在Holder类里声明prop字段时,prop本身还没定义,编译器会直接报错。对应的伪代码如下:
template<size_t FieldOffset> class Property { class Holder* GetOuter() { return (char*)this - FieldOffset; // 通过字段偏移量反推外层对象地址 } } class Holder { int Field1; std::string Field2; Property<offsetof(Holder, prop)> prop; // 错误!此时prop还未完成定义 }
可行的实现方案
方案1:反向传递外层类型+成员指针,延迟偏移计算
把Property的模板参数改成接收外层Holder类型和成员指针,在GetOuter函数里动态计算偏移量,这样就不用在Holder定义阶段就知道prop的偏移:
template<typename OuterType, OuterType OuterType::*MemberPtr> class Property { public: OuterType* GetOuter() { // 先计算成员指针对应的偏移量,再用当前Property地址减去偏移得到Holder地址 return reinterpret_cast<OuterType*>( reinterpret_cast<char*>(this) - reinterpret_cast<size_t>(&(reinterpret_cast<OuterType*>(0)->*MemberPtr)) ); } }; // 前置声明Holder,提前定义成员指针类型解决循环依赖 class Holder; using PropMemberPtr = Property<Holder, &Holder::prop> Holder::*; class Holder { public: int Field1; std::string Field2; Property<Holder, &Holder::prop> prop; };
这里关键是前置声明Holder并提前定义成员指针类型,让编译器能正确识别&Holder::prop的类型。
方案2:嵌套类+CRTP模式
把Property作为Holder的嵌套类,通过CRTP(奇异递归模板模式)把外层类型传递给基类,基类负责计算偏移:
template<typename Derived, auto MemberPtr> class PropertyBase { public: Derived* GetOuter() { return reinterpret_cast<Derived*>( reinterpret_cast<char*>(this) - reinterpret_cast<size_t>(&(reinterpret_cast<Derived*>(0)->*MemberPtr)) ); } }; class Holder { public: int Field1; std::string Field2; // 嵌套定义Property,继承自带外层类型的基类 class Property : public PropertyBase<Holder, &Holder::prop> { // 这里可以添加Property的业务逻辑 } prop; };
这种方式把Property和Holder的绑定关系放在嵌套类里,逻辑更紧凑。
方案3:在函数内动态计算偏移(C++11+)
把偏移计算从模板参数移到GetOuter函数内部,这样Property的声明不需要提前绑定到特定字段,但调用时需要指定成员指针:
template<typename OuterType> class Property { private: template<OuterType OuterType::*MemberPtr> static size_t getOffset() { return reinterpret_cast<size_t>(&(reinterpret_cast<OuterType*>(0)->*MemberPtr)); } public: template<OuterType OuterType::*MemberPtr> OuterType* GetOuter() { return reinterpret_cast<OuterType*>( reinterpret_cast<char*>(this) - getOffset<MemberPtr>() ); } }; class Holder { public: int Field1; std::string Field2; Property<Holder> prop; void test() { // 调用时指定当前prop对应的成员指针 Holder* outer = prop.GetOuter<&Holder::prop>(); } };
这个方案的灵活性更高,但每次调用GetOuter都要传成员指针,略显繁琐。
注意事项
- 所有方案都必须基于标准布局类型,否则偏移计算的行为是未定义的
- C++标准规定空类大小至少为1字节,所以
sizeof(Property)最小只能是1字节,没法做到真正的0字节 - 代码中用到的
reinterpret_cast是必要的,但只能在标准布局类型上使用,确保类型转换的安全性
内容的提问来源于stack exchange,提问作者Artem Selivanov
相关产品推荐
相关产品推荐

