关于使用同一指针初始化基类与派生类成员是否存在未定义行为(UB)的技术问询
看起来你已经精准定位了初始实现里的核心问题——在基类构造时调用的makeProject试图访问还未初始化的project_成员,这确实会触发UB,还好被ASAN抓了出来。你的修复方案用委托构造函数重构了初始化逻辑,整体思路非常合理,咱们逐个拆解你关心的点,确认安全性:
1. std::move(project)会不会让之前get()返回的指针失效?
完全不会。std::unique_ptr的移动操作只是转移对象的所有权,不会改变指针指向的内存地址。在委托构造函数里,我们先把*project.get()传递给基类构造函数(此时Project对象已经被std::make_unique正确构造,内存地址固定),之后才将project移动到project_成员中。整个过程中,Project对象本身没有被移动或销毁,基类持有的引用始终指向有效的对象,这一步完全安全。
2. project_能不能正确销毁Project对象?
没问题。当FakeResourceManager被销毁时,C++的析构顺序是:
- 先执行
FakeResourceManager的析构函数体 - 再销毁派生类的成员变量
project_(此时unique_ptr会自动释放持有的Project对象) - 最后执行基类
ResourceManager的析构函数体
只要基类的析构函数不访问那个Project&,Project对象的销毁时机就不会有问题,project_能正确完成清理工作。
3. 基类持有的悬空引用会不会导致UB?
这里要明确一个C++标准的细节:持有悬空引用本身不是UB,只有当你试图使用(比如访问成员、调用函数)悬空引用时才会触发UB。
在你的方案中:
- 当
FakeResourceManager存活时,project_管理的Project对象也活着,基类的Project&是有效的,任何基类成员函数访问这个引用都没问题; - 只有当
project_销毁Project之后(也就是派生类成员销毁阶段),基类的引用才会变成悬空状态。但只要ResourceManager的析构函数和其他后续逻辑不访问这个悬空引用,就不会产生UB。
如果ResourceManager的析构函数确实会访问这个引用,那才会出问题——但你已经提到它不会,所以这一点无需担心。
4. 关于设计选择:委托构造 vs 静态工厂函数
你说的没错,静态工厂函数也能实现类似的逻辑,但委托构造的方式更简洁,还能保持构造函数的封装性,没有明显的劣势,这个选择是合理的。
总结
你的修复方案不存在未定义行为,逻辑上是安全的,只要保证ResourceManager的所有成员函数(包括析构函数)不会在Project对象销毁后访问持有的引用即可。
内容来源于stack exchange

