6d1895d696
- pitfall_stack_widget / pitfall_lambda_capture / pitfall_double_connect: 默认跑正确写法(离屏验证退出码 0),--crash / --dangle 开关课堂现场触发 真实崩溃/悬空访问 - table_widget:QTableWidget 单元格事件最小示例(新安排 Day4「表格事件」) - student_manager:Day5 综合案例参考实现,离屏自测覆盖 JSON round-trip 与 QThread 后台导入闭环 - 均接入 examples/CMakeLists.txt 的 add_teaching_example,手工维护区不影响生成器
坑点复现:栈对象与父子所有权打架(双重释放)—— 教学补充示例
对应教案
OUTLINE_4DAY.mdDay2 上午 ★坑卡 「栈对象与父子所有权打架」。手工维护,非tools/gen_part3.py生成产物。
概念
Qt 的父子树是一套所有权机制:parent 析构时会 delete 它的所有子对象。
这隐含一个硬性前提——交给父子树管的对象必须建在堆上。一个对象只能有一个
所有者:给了 parent,就不能再由栈作用域、智能指针或手动 delete 管理。
错在哪(--crash 演示的代码)
QPushButton btn("我建在栈上"); // 先声明 → 后析构
QWidget window; // 后声明 → 先析构
btn.setParent(&window);
// 离开作用域:window 先析构 → 父子树 delete &btn(栈地址!)→
// glibc 报 "free(): invalid pointer" 后 abort
注意声明顺序反过来(parent 先声明)时这段代码恰好不崩——btn 先析构时会把
自己从 parent 的子对象列表里摘除。「有时不崩」正是这类所有权错误难查的原因:
不要依赖析构顺序的运气,规则只有一条——给了 parent 就必须在堆上。
验证与演示方式
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与堆版/WA_DeleteOnClose版的三方对照。
参考
- 本仓库对照:
p03/ch03/button_creation.cpp(正确的 parent 用法) - 前置概念:Day1 ★坑卡「所有权心智模型:谁负责 delete」