# 坑点复现:栈对象与父子所有权打架(双重释放)—— 教学补充示例 > 对应教案 [`OUTLINE_4DAY.md`](../../OUTLINE_4DAY.md) Day2 上午 ★坑卡 > 「栈对象与父子所有权打架」。手工维护,非 `tools/gen_part3.py` 生成产物。 ## 概念 Qt 的父子树是一套**所有权**机制:parent 析构时会 `delete` 它的所有子对象。 这隐含一个硬性前提——**交给父子树管的对象必须建在堆上**。一个对象只能有一个 所有者:给了 parent,就不能再由栈作用域、智能指针或手动 `delete` 管理。 ## 错在哪(`--crash` 演示的代码) ```cpp QPushButton btn("我建在栈上"); // 先声明 → 后析构 QWidget window; // 后声明 → 先析构 btn.setParent(&window); // 离开作用域:window 先析构 → 父子树 delete &btn(栈地址!)→ // glibc 报 "free(): invalid pointer" 后 abort ``` 注意声明顺序反过来(parent 先声明)时这段代码**恰好不崩**——`btn` 先析构时会把 自己从 parent 的子对象列表里摘除。「有时不崩」正是这类所有权错误难查的原因: 不要依赖析构顺序的运气,规则只有一条——**给了 parent 就必须在堆上**。 ## 验证与演示方式 ```bash QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_pitfall_stack_widget # 正确写法,退出码 0 QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_pitfall_stack_widget --crash # 课堂演示:现场崩给学员看 ``` `--crash` 必然非正常退出(core dump),**不要**放进自动化验证。 ## 练习方向(供任务卡引用) - 把错误版本的两行声明顺序对调,观察「恰好不崩」,再解释为什么它仍然是错的; - 同一误区的第三次出现:非模态对话框建在栈上一闪而过,见 [`p03/ch06/modeless_dialog_stack.cpp`](../../../../p03/ch06/modeless_dialog_stack.cpp) 与堆版/`WA_DeleteOnClose` 版的三方对照。 ## 参考 - 本仓库对照:[`p03/ch03/button_creation.cpp`](../../../../p03/ch03/button_creation.cpp)(正确的 parent 用法) - 前置概念:Day1 ★坑卡「所有权心智模型:谁负责 delete」