上个月我让大模型帮我写一个后台任务面板:一个 QThread 跑耗时计算,算完把进度回传给主线程的窗口刷新。模型给的代码编译零警告,界面也能弹出来,点了"开始"之后进度条走得挺顺。
然后我点了关闭窗口。
进程直接没了,连 core dump 都懒得留。第二天我在日志里翻到一行 Segmentation fault,时间戳是凌晨三点零七分——那是我睡前随手关掉窗口的时刻。
这段经历让我重新想了一个问题:在 C++/Qt 这种"语法随便过、运行时处处是隐式契约"的领域,AI 到底帮我们省了什么,又偷偷埋了什么。
Qt 最迷人也最危险的设计,是 QObject 的父子对象树:父对象析构时会 delete 所有子对象。这省去了手写 delete 的麻烦,也埋下了最经典的一颗雷。
// 看起来毫无问题,编译通过,运行崩溃
QWidget parent;
QPushButton btn("ok", &parent); // 栈对象,却被登记为 parent 的子对象
// 作用域结束时,btn 先析构,parent 再析构时又 delete 一次 → double free模型生成这类代码时不会犹豫,因为从语法上看它完全合法。它不知道"栈上对象不该设 parent"这条约定——这条约定不在语言规范里,在 Qt 的对象模型里,在一代代开发者踩坑的肌肉记忆里。
我把上下文补给它:"这个按钮的生命周期由谁管理?"它才改成了堆分配加 parent。但问题是,在我问出这句话之前,它没有任何动机去思考所有权。
第二个坑在跨线程通信。Qt 的信号槽有一套很聪明的连接类型推断:同线程直连,跨线程排队。听起来自动,其实处处是前提。
// worker 在子线程,this 在主线程
connect(worker, &Worker::progress, this, [this](int v) {
ui->progressBar->setValue(v); // 危险:如果没传 context 呢?
});上面这段因为第三个参数 this 作为 context 存在,Qt 会在 this 销毁时自动断开,跨线程也会走排队连接,基本安全。但如果模型图省事,写成两参数版本、把 this 塞进 lambda 捕获列表:
connect(worker, &Worker::progress, [this](int v) { ui->progressBar->setValue(v); });接收者变成了"无上下文",连接不会随窗口销毁而断开。窗口一关,this 悬垂,下一次 progress 信号到来时——就是你凌晨三点看到的那一幕。更糟的是,这个版本连线程亲和性判断都失去了依据,GUI 更新可能直接在子线程里执行。
模型两段都会写,而且不会主动告诉你哪段更危险。因为在它的"世界观"里,这两种写法都叫"连接信号和槽"。
还有一种崩溃更隐蔽:Q_OBJECT 宏忘了加,或者 CMake 里没开 AUTOMOC。代码能编过,运行时信号槽静默失效,connect 返回 false 而你从没检查返回
if (!connect(btn, &QPushButton::clicked, this, &MainWindow::onClicked)) {
qWarning() << "connect failed"; // 这行几乎没人写,但能救命
}模型很少主动生成这个检查,因为它默认"连接一定成功"。而 Qt 的老手都知道,连接失败是静默的,是那种"功能没反应但也不报错"的慢性病。
回头看,模型给我的每一段代码都是对的——单独看都对。问题出在它们拼在一起时的隐式契约:对象树的所有权、线程的亲和性、连接的生命周期、构建系统的宏展开。这些东西没有一条写在函数签名里,却决定了程序是跑一年还是跑三秒。
C++/Qt 恰好是这类"隐式契约"密度极高的技术栈。语言本身给你自由,Qt 在上层加了一整套运行时规矩,而 AI 擅长的是语法层面的模式匹配,不擅长的是"这套规矩在你这个项目里意味着什么"。
所以我现在让模型写 Qt 代码的流程变成了三步:让它写第一版,我自己审一遍所有权和线程,然后在每个 connect 旁边问一句"谁负责断开"。代码量几乎没变,但崩在凌晨三点的次数,肉眼可见地少了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。