教学示例补 5 个:3 个坑点复现 + QTableWidget + Day5 学员管理器参考实现

- 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,手工维护区不影响生成器
This commit is contained in:
张宗平
2026-07-05 12:49:20 +08:00
parent 6640baffae
commit 6d1895d696
11 changed files with 798 additions and 1 deletions
@@ -0,0 +1,37 @@
# 坑点复现:重复 connect 导致槽触发多次 —— 教学补充示例
> 对应教案 [`OUTLINE_4DAY.md`](../../OUTLINE_4DAY.md) Day2 下午 ★坑卡
> 「重复 connect 导致槽触发多次」。手工维护,非生成产物。
## 概念
`connect` 每调用一次就**新增一条**连接——Qt 不会去重。同一对信号槽连了两次,
信号一发槽就被调两次。典型事故现场:`connect` 写在会被多次执行的路径上
(每次打开对话框/每次刷新界面都跑一遍的「初始化」函数)。
现象非常有迷惑性:「点一次按钮弹两次框」——学员的第一反应是查按钮、查事件,
而根源在连接次数。
## 两种修复
1. **把 connect 收拢到只执行一次的地方**(构造函数)——首选,从结构上杜绝;
2. `Qt::UniqueConnection`——同一对(发送者,信号,接收者,槽)只允许一条连接。
**注意它只对成员函数指针有效**:lambda 没有可比较的身份,UniqueConnection
对 lambda 连接不起作用(这是它常被误用的点)。
## 验证方式
```bash
QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_pitfall_double_connect # 确定性输出:2 次 → 1 次,退出码 0
```
## 练习方向(供任务卡引用)
-`Popup::showOnce` 换成 lambda 再加 `Qt::UniqueConnection`,验证「lambda
防不了重」;
-`showOnce` 里打印 `sender()`,观察两次调用来自同一发送者的两条连接。
## 参考
- 本仓库对照:[`p03/ch04/custom_signal_slot.cpp`](../../../../p03/ch04/custom_signal_slot.cpp)
- Qt 官方文档:`QObject::connect``Qt::ConnectionType` 一节,选读·需联网)
@@ -0,0 +1,69 @@
// ============================================================================
// qt_pitfall_double_connect —— 坑点最小复现:重复 connect 导致槽触发多次
// 对应教案:OUTLINE_4DAY.md Day2 下午 ★坑卡「重复 connect 导致槽触发多次」
// 配套文档:docs/teaching/examples/pitfall_double_connect/README.md
//
// 现象还原:「点一次按钮弹两次框」——根源不在按钮,在 connect 被执行了两次。
// 同时演示 Qt::UniqueConnection 防重(只对成员函数指针有效,lambda 用不了)。
// 确定性输出,无 UB,离屏/终端直接跑,退出码 0。
// ============================================================================
#include <QCoreApplication>
#include <QDebug>
#include <QObject>
class Button : public QObject {
Q_OBJECT
public:
using QObject::QObject;
signals:
void clicked();
};
class Popup : public QObject {
Q_OBJECT
public:
using QObject::QObject;
int shownCount = 0;
public slots:
void showOnce() {
++shownCount;
qInfo() << " 弹框!(第" << shownCount << "次)";
}
};
int main(int argc, char* argv[]) {
QCoreApplication app(argc, argv);
Button btn;
Popup popup;
// 典型事故现场:connect 写在会被反复执行的函数里(比如每次打开某个界面
// 都调一次 setup()),每调一次就多一条连接——信号一发,槽被调 N 次。
auto setup = [&]() { QObject::connect(&btn, &Button::clicked, &popup, &Popup::showOnce); };
setup();
setup(); // 第二次“初始化”——现实里往往藏在另一条调用路径上
qInfo() << "重复 connect 两次后,点一次按钮:";
emit btn.clicked();
qInfo() << "槽被调用了" << popup.shownCount << "次(预期演示值:2";
// 修复一:连接前先断开旧连接
QObject::disconnect(&btn, &Button::clicked, &popup, &Popup::showOnce);
QObject::disconnect(&btn, &Button::clicked, &popup, &Popup::showOnce);
// 修复二:Qt::UniqueConnection——同一对(发送者,信号,接收者,槽)只允许一条连接。
// 注意它只认成员函数指针;lambda 没有身份,UniqueConnection 对 lambda 无效。
popup.shownCount = 0;
QObject::connect(&btn, &Button::clicked, &popup, &Popup::showOnce, Qt::UniqueConnection);
QObject::connect(&btn, &Button::clicked, &popup, &Popup::showOnce, Qt::UniqueConnection);
qInfo() << "改用 Qt::UniqueConnection 连两次后,点一次按钮:";
emit btn.clicked();
qInfo() << "槽被调用了" << popup.shownCount << "次(预期演示值:1";
qInfo() << "结论:connect 不要写在会被多次执行的路径上;防御手段是"
"UniqueConnection(仅限成员函数槽)或把 connect 收拢到构造函数里只做一次。";
return 0;
}
#include "qt_pitfall_double_connect.moc"