std::make_shared与shared_ptr(new)初始化抽象子类行为异常排查
背景
在Qt Creator 13.0.0开发计算器应用,基于SOLID原则重构类结构:将expression_element设为抽象基类,派生出value_interface、operator_interface等子类,其中value继承自value_interface。出现异常现象:
- 使用
std::make_shared<value>(val)初始化的对象中,contained_value为无效值(NaN),最终导致程序崩溃 - 栈对象
value(val)和std::shared_ptr<value>(new value(val))初始化的对象中,contained_value值完全正常
可能的原因排查方向
1. 构造函数重载匹配错误
如果value类存在多个构造函数(比如包含接受std::shared_ptr、const char*或其他可隐式转换类型的重载),std::make_shared推导模板参数时可能匹配到错误的构造函数,导致val未被正确传递给负责初始化contained_value的目标构造函数。而直接使用new value(val)或栈对象构造时,编译器能明确匹配到正确的构造函数,避免了参数推导歧义。
2. 拷贝/移动构造函数实现缺陷
std::make_shared的内部实现可能会触发对象的拷贝或移动操作(部分编译器实现会先构造临时对象再转移所有权)。如果value类的拷贝构造函数或移动构造函数未正确处理contained_value——比如遗漏了该成员的赋值,或者移动构造时错误地将原对象的contained_value置为NaN——就会导致最终生成的shared_ptr指向的对象中该值无效。而直接new或栈对象构造时,可能未触发这些有问题的拷贝/移动逻辑。
3. 内存布局与编译器优化导致的未定义行为
std::make_shared会一次性分配对象内存和智能指针的控制块内存,这种连续内存布局和new单独分配对象内存的布局不同。如果Qt Creator对应的编译器(如GCC、Clang或MSVC的特定版本)存在优化bug,或者value类的成员变量存在内存对齐问题,就可能触发未定义行为,导致contained_value的内存位置被错误覆盖或未正确初始化。
4. 临时对象生命周期问题
如果val是一个临时对象,且value的构造函数接受的是引用类型,std::make_shared的构造时序可能导致临时对象在value构造完成前提前销毁,使得构造函数接收到无效的引用,进而导致contained_value初始化失败。而直接使用new value(val)或栈对象构造时,临时对象的生命周期足以覆盖构造过程。
5. 模板参数误写(低概率)
确认std::make_shared的模板参数确实是value而非其基类value_interface。如果误写为基类,可能因对象切片导致contained_value未被正确初始化,但此情况在代码明确指定value时概率极低,可快速排查。
内容的提问来源于stack exchange,提问作者Pavlo Tkach

