# 坑点复现：栈对象与父子所有权打架（双重释放）—— 教学补充示例

> 对应教案 [`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」
