Mixin构造函数中获取派生类数据成员地址的合规实现
问题描述
我编写了如下C++代码,希望让所有继承RegisterMixin的派生类对象在构造时,自动将其数据成员地址注册到全局映射供外部系统管控:
// Base class to make derived objects polymorphic struct Base { virtual ~Base() = default; }; void registerData(int*) {} template <typename Derived> struct RegisterMixin { static int (Derived::*dataPtr); // Defined for each derived class RegisterMixin() { auto* derived = static_cast<Derived*>(this); registerData(&(derived->*dataPtr)); } }; struct MyDerived: Base, RegisterMixin<MyDerived> { int myData; // Will be set by the enclosing system }; template <> int MyDerived::*RegisterMixin<MyDerived>::dataPtr = &MyDerived::myData; int main() { MyDerived derived; }
但用UB sanitizer检测时,发现RegisterMixin构造函数里的static_cast<Derived*>(this)存在未定义行为——因为Mixin作为基类,其构造函数会先于派生类执行,此时派生类对象尚未完成构造,无法安全转换为派生类类型。我只需要获取数据成员的地址,请问怎么改才能避免UB?
解决方案
问题的核心是基类构造阶段无法安全访问派生类对象。我们需要把注册动作延迟到派生类对象内存完全分配、可以合法访问成员地址的阶段执行。
修改后的代码
// Base class to make derived objects polymorphic struct Base { virtual ~Base() = default; }; void registerData(int*) {} template <typename Derived> struct RegisterMixin { static int (Derived::*dataPtr); // 每个派生类对应一个成员指针 // 辅助注册类:在构造时完成成员地址注册 struct Registrar { explicit Registrar(Derived* obj) { registerData(&(obj->*dataPtr)); } }; }; struct MyDerived: Base, RegisterMixin<MyDerived> { int myData; // 由外部系统设置的成员 // 声明辅助注册成员,通过默认初始化触发注册逻辑 typename RegisterMixin<MyDerived>::Registrar registrar{this}; }; template <> int MyDerived::*RegisterMixin<MyDerived>::dataPtr = &MyDerived::myData; int main() { MyDerived derived; }
为什么这样可行
- 构造顺序的保证:C++对象的构造流程是「基类构造 → 成员变量构造 → 派生类构造函数体执行」。这里的
registrar是MyDerived的成员变量,它的构造会在所有基类(Base和RegisterMixin)构造完成后执行。此时MyDerived对象的内存已经完全分配,this指向的是合法的MyDerived实例。 - 合法的地址获取:在
Registrar的构造函数中,我们只获取成员的内存地址,不需要访问成员的值——即使成员尚未初始化,其地址也是有效的,完全符合需求。
额外优化(可选)
如果想避免在每个派生类中重复写registrar声明,可以用模板别名简化写法:
template <typename Derived> using RegisterHelper = typename RegisterMixin<Derived>::Registrar; // 派生类中可以写成: RegisterHelper<MyDerived> registrar{this};
内容的提问来源于stack exchange,提问作者chrysante
相关产品推荐
相关产品推荐

