尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt窗口显示隐藏与关闭:生命周期、销毁策略与避坑实践

Qt窗口显示隐藏与关闭:生命周期、销毁策略与避坑实践 1. 从一个窗口的生死说起QT窗口显示与隐藏到底在做什么很多人第一次接触QT是从拖控件开始的。MainWindow拖出来按钮一放信号槽一连编译跑起来窗口弹出来任务就算完成了。等到项目稍微做大一点问题就来了登录窗口验证通过后要关掉、主界面要弹出来设置面板要能反复开合托盘图标点一下主窗口要最小化到后台、再点一下要还原某个子窗口关闭的时候不能真的销毁因为里面还存着用户填了一半的数据。这时候你才发现QT窗口的显示和隐藏、关闭根本不是一句 show() 和 hide() 就能打发的事情。这篇文章要解决的就是这类问题。它适合已经能写出第一个QT窗口、但对窗口生命周期管理还比较模糊的朋友也适合做了几个项目、被窗口关了程序没退出窗口隐藏了内存还在涨子窗口一关主窗口跟着挂这些问题折磨过的开发者。我不会只给你API清单那种东西官方文档比你我都写得全。我要讲的是这些API背后发生了什么、什么场景该用哪个、用错了会踩什么坑、以及我在实际项目里验证过的处理套路。先说一个最容易被忽略的前提。QT里所谓的窗口本质上是 QWidget 及其子类而 QWidget 有一个非常关键的性质它可以不显示但依然存在。这句话是整个话题的根。一个窗口对象被 new 出来之后即使从来没调用过 show()它也是实实在在占着内存的、它的成员变量、它的信号槽连接、它持有的数据全都活着。显示和隐藏只是控制这块绘制区域要不要出现在屏幕上关闭则涉及到这个对象要不要被销毁。把这三种状态分清楚后面的所有选择才有依据。我见过太多代码把 hide() 和 close() 混着用功能测试时看着都对因为界面上确实看不见了。但跑上几个小时内存曲线一直往上走或者改了个配置再打开设置面板发现里面还是上次的旧值甚至更邪门的——子窗口关了两次程序直接崩掉。这些都是窗口生命周期没管明白的直接后果。接下来我按整体设计思路 → 核心API细节 → 实操流程 → 问题排查这条线往下讲每一块都会给到能直接抄的代码和参数说明。你如果现在手上正好有个多窗口的项目可以边看边对照自己的代码改。2. 窗口显示隐藏关闭的整体设计思路2.1 三种状态、两条路径先在心里画清楚要在项目里把窗口管理做对得先建立一个清晰的心智模型。QT里一个 QWidget 对象的生命周期可以拆成三种可见性状态和两条销毁路径。三种状态分别是未显示hidden but alive对象已构造但没有出现在屏幕上。hide() 之后、show() 之前都是这个状态。此时它的 paintEvent 不会被触发但它依然响应信号槽、依然持有数据。已显示visible调用了 show()、showNormal()、showMaximized() 等之后的状态窗口出现在屏幕上参与事件循环的绘制和鼠标键盘事件分发。已销毁destroyed对象被 delete或者因为父对象析构而被连带销毁或者设置了 Qt::WA_DeleteOnClose 后在 close 时自动销毁。销毁之后这个指针就是野指针再碰它必崩。两条销毁路径则是手动接管自己 new、自己 delete或者交给 QScopedPointer、std::unique_ptr 管理。父子托管new 的时候传了 parent父对象析构时自动 delete 所有子对象。这是QT对象树的核心机制也是最省心的方式。把这两组概念交叉一下关闭这个动作的语义就清楚了close() 默认只是把窗口从屏幕上拿掉并不销毁对象。它发出 QCloseEvent你可以在 event 里 accept 或 ignore。真正决定对象生死的是另外的机制——要么设了 WA_DeleteOnClose 属性要么父对象被销毁要么你手动 delete。这就是为什么很多人的登录窗口关闭之后用 QApplication::allWidgets() 还能把它捞出来指针还有效但屏幕上没了。这不是bug是设计。2.2 为什么不用关闭即销毁做默认策略有人会问既然要关掉为什么不干脆销毁省内存这就要说到选型逻辑了。我早期做过一个串口配置面板用户点参数设置就弹出来点确认就关掉。当时图省事在关闭时直接 deleteLater()。问题很快暴露用户调参数是高频操作反复开合每次都要重新构造整个面板、重新建立串口列表、重新读一遍当前配置界面明显卡顿而且构造过程中还有个几次毫秒的空白闪烁。后来改成面板只创建一次反复 show() 和 hide()流畅度立刻正常了。反过来也有例子。一个批量任务结果展示窗口里面可能塞几万行日志和缩略图。这种窗口如果藏起来不删内存就一直占着用户开着好几个内存直接爆。这种场景就必须用完即销毁甚至销毁前要手动清空模型数据。所以判断标准其实很朴素场景特征推荐策略理由打开频繁、内容基本不变、构造开销大单例 show/hide 复用避免反复构造响应快打开次数少、数据量大、内存敏感创建后关闭即销毁及时释放内存需要保留用户输入、切换后要回到原状态隐藏不销毁状态得以保留模态对话框、用完即走、无状态栈上对象或 deleteLater代码简单无泄漏风险这张表我在多个项目里反复用基本没出过大错。核心就一句这个窗口贵不贵构造代价和内存占用以及要不要记住上次的样子。贵且要记状态就复用便宜或不记状态就销毁。2.3 主窗口与子窗口的关系怎么摆还有一个结构性问题主界面和弹出的子窗口谁当谁的父亲。QT的对象树规则是给构造函数传 parent 的对象会被父对象托管父对象析构时一起析构。子窗口传了主窗口指针当 parent好处是生命周期自动绑定不会漏删坏处是如果不想要这种绑定就会出问题。我踩过的坑是这样的主窗口作为 parent子窗口设置为 Qt::Window 类型独立顶层窗口外观用户点主窗口的关闭按钮主窗口析构子窗口跟着被销毁。但如果子窗口当时还开着、并且用户正在里面填数据就会看到子窗口莫名其妙消失。这不是错误是父子托管在起作用。要避免要么子窗口的 parent 传 nullptr 自己管要么在关闭主窗口前先妥善处理所有子窗口。我现在的习惯是功能面板、设置窗口这类跟随主界面存在的传 parent工具窗口、需要独立出现在任务栏的parent 传 nullptr用单独的智能指针或者 QPointer 管理。QPointer 特别值得推荐它是弱引用对象被销毁后它会自动变成 nullptr判断if (ptr)就知道窗口还在不在比裸指针安全得多。3. 核心API逐个拆解show、hide、close到底做了什么3.1 show 家族不只是让窗口出现show() 是显示窗口的入口但它内部做的事情比名字看起来多。调用 show() 时QT会做几件事评估窗口的合适尺寸如果没有显式设置尺寸会根据布局和 sizeHint 计算、决定是否触发首次绘制、把窗口标记为可见并提交给窗口系统、最后进入事件循环等待绘制事件。show 家族里还有几个近亲语义差别必须分清showNormal()以正常状态显示如果窗口之前是最小化或最大化状态会恢复到普通大小。这在托盘还原场景里特别有用。showMaximized()最大化显示。showMinimized()最小化显示窗口还在但收成任务栏或图标。showFullScreen()全屏显示没有标题栏边框用于展示、看板场景。这里有个实践细节值得说。从最小化状态还原如果你只调 show()窗口可能还保持着最小化标志表现就是调了没用。正确的做法是showNormal()或者先setWindowState(Qt::WindowNoState)再 show。我在做托盘还原功能时就被这个坑过日志里明明打印了 show 调用成功界面上就是不出来查了半天才发现状态标志没清。另外重复调用 show() 是安全的但也不是零成本。QT内部有可见性判断已经可见的窗口再 show() 不会重复触发完整流程但一些关联的 setVisible 逻辑还是会走一遍。如果一个窗口在很短周期内被高频 show/hide比如某些自定义动画里建议改成判断当前状态再决定是否调用避免不必要的重绘。3.2 hide 的真正含义从屏幕消失但依然活着hide() 做的是把窗口标记为不可见从屏幕上移除但对象本身完全保留。这个保留包括所有成员变量、控件状态、用户输入的内容所有的信号槽连接窗口的位置和尺寸记忆下次 show 还在原位置它如果设置了布局和子控件这些子控件的状态也全在这就是隐藏不销毁策略的实现基础。我用它做过多步骤表单第一步填完点下一步把当前面板 hide下一个面板 show用户点上一步回来之前填的内容一个不差。如果每步都销毁重建要额外做一套数据持久化逻辑工作量翻倍还容易漏字段。不过 hide 有个容易忽略的点窗口隐藏之后它的定时器依然在跑信号槽依然会响应。有人隐藏了一个带轮询的窗口以为它停了其实后台还在不停发请求。如果某些操作应该在窗口不可见时暂停你得显式处理比如在 hideEvent 里停掉定时器在 showEvent 里再启动。这个 showEvent 和 hideEvent 是配套的重写它们可以精确感知窗口的显示状态变化。注意hide() 不会触发关闭事件也不会发出 close 相关的信号。如果你希望隐藏时执行某些清理逻辑必须靠 hideEvent别指望 closeEvent。3.3 close 的双重身份与关闭事件的拦截close() 是最容易被误解的一个。它的语义是请求关闭窗口而且这个请求是可以被拒绝的。调用 close() 时QT会向窗口发送一个 QCloseEvent。窗口的 closeEvent() 函数被调用你能拿到这个 event 对象然后决定接受还是忽略void MyWidget::closeEvent(QCloseEvent *event) { if (m_hasUnsavedChanges) { QMessageBox::StandardButton ret QMessageBox::warning( this, QStringLiteral(提示), QStringLiteral(有未保存的修改确定要关闭吗), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (ret QMessageBox::Cancel) { event-ignore(); // 拒绝关闭窗口留在屏幕上 return; } if (ret QMessageBox::Save) { saveAll(); } } event-accept(); // 接受关闭 }这段代码是窗口关闭保护的典型写法。关键点在于event-ignore()它让关闭动作无效窗口继续存在。很多新手写关闭确认时弹框点取消后窗口还是没了就是因为忘了 ignore。还有一个细节close() 被接受后窗口默认只是隐藏不销毁。要让它销毁两个办法在 closeEvent 里显式deleteLater()或者构造时设置setAttribute(Qt::WA_DeleteOnClose)。后者更省事但记得它只对顶层窗口或独立窗口有意义普通子控件设了不会有预期效果。如果窗口是程序的主窗口或者它是最后一个可见的顶层窗口close 被接受后QApplication 可能会触发最后一个窗口关闭逻辑进而 quit 整个程序。这个行为受QApplication::setQuitOnLastWindowClosed(bool)控制默认是 true。做托盘驻留类程序时必须把它设成 false否则用户一点关闭主窗口没了程序整个退出托盘图标也跟着消失这是托盘程序最常见的翻车点。3.4 deleteLater 与直接 delete 的取舍销毁窗口时大概率你会看到两种写法delete w;和w-deleteLater();。它们不是可以随意互换的。直接 delete 会立刻析构对象。如果这个时机落在对象自己的事件处理函数里或者落在它正在处理的信号槽调用栈中间delete 之后调用栈返回时还会访问到已经释放的内存直接崩。这就是著名的在槽函数里删自己问题。deleteLater() 则是向事件循环投递一个删除请求等当前事件处理结束、控制权回到事件循环时才真正删除。这样避免了上述的自删崩溃。所以结论很明确只要删除动作可能发生在对象自身的事件流中包括各种信号触发的槽、事件处理函数一律用 deleteLater()。那什么时候可以直接 delete在对象的构造和事件处理之外的、你能明确知道调用栈不会回到该对象的上下文里比如在另一个完全不相关的管理类里清理一个已经确认不再使用的窗口。但说实话为了省那几个字节的开销去赌调用栈不值得。我现在几乎所有窗口销毁都走 deleteLater()除非有明确的性能理由。提示使用 WA_DeleteOnClose 属性时QT内部走的就是 deleteLater 机制所以它是安全的。这也侧面说明官方推荐的销毁方式就是延后删除。4. 多窗口切换的实操登录、主界面、设置面板怎么串4.1 登录窗口到主界面的正确交接方式登录到主界面是几乎每个桌面程序都要面对的第一道多窗口题。看一个我在项目里用了很久的写法。假设登录窗口是 LoginDialog主窗口是 MainWindow。如果直接这么写// 错误示范登录成功后在登录窗口里直接构造主窗口 void LoginDialog::onLoginSuccess() { MainWindow *w new MainWindow(); w-show(); this-close(); // 看起来没问题 }这段代码运行时可能碰巧正常但隐患不少。第一MainWindow 没有父对象成了野生的顶层窗口程序退出时如果没被正确处理可能残留或者泄漏。第二登录窗口 close 之后如果没设 WA_DeleteOnClose它一直占着内存。第三如果后面还要从这个主窗口返回到登录比如切换账号你发现登录窗口的数据还停留在上次的状态因为它根本没销毁。更稳的写法是把控制权交给一个统一的入口比如 main 函数或者一个专门的 Controller// main.cpp 里的流程控制 int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setQuitOnLastWindowClosed(false); // 手动控制退出时机 LoginDialog login; if (login.exec() ! QDialog::Accepted) { return 0; // 用户取消登录直接退出 } MainWindow mainWin; mainWin.show(); return app.exec(); }这里用 QDialog::exec() 以模态方式跑登录返回 Accepted 才继续。登录对话框的生命周期由栈管理函数退出自动析构干净。主窗口活在主函数作用域内程序结束自然释放。这种入口统一调度、窗口各自管好自己的结构比在窗口里互相 new 要可控得多尤其是在有多个入口、需要反复登录切换的场景。如果你的登录是无边框自定义标题栏的窗口exec() 也照样能用只是要在构造里设好 Qt::FramelessWindowHint 和相关属性。模态性只影响事件分发跟外观无关。4.2 设置面板复用单例加显示切换设置面板是贵且要记状态的典型。用单例加 show/hide 复用最合适class SettingsPanel : public QWidget { public: static SettingsPanel *instance() { static SettingsPanel panel; // 静态局部C11 起线程安全 return panel; } void popup() { refreshFromConfig(); // 每次显示前同步最新配置 show(); raise(); // 提到最前 activateWindow(); // 抢焦点 } protected: SettingsPanel(QWidget *parent nullptr) : QWidget(parent) {} void closeEvent(QCloseEvent *e) override { saveToConfig(); // 关闭前保存 hide(); e-ignore(); // 关键拒绝真正的关闭转为隐藏 } };这段代码有几个设计点值得琢磨。static SettingsPanel panel;用的是静态局部变量好处是首次调用时构造、程序结束时析构不用手动管理也不用担心泄漏。但注意静态局部变量不能传 parent因为它不属于对象树这没问题因为它是复用型的常驻面板。closeEvent 里用e-ignore()把关闭转成隐藏是关闭即隐藏这个操作习惯的标准实现。很多应用的用户点窗口的 X 按钮期望的就是收起来而不是彻底关掉再重开。popup()里先刷新配置再显示是因为配置可能被别处修改过每次显示都应该是最新的。raise()和activateWindow()的组合是用来把窗口从可能被遮挡的状态提到前台如果窗口之前被隐藏或者被别的窗口压住这两个调用能确保它出现在用户眼前。实测下来单独用 raise() 有时不够配合 activateWindow() 更稳尤其是在 Windows 上。注意如果面板是单例且用静态局部变量实现就不要在它上面设 WA_DeleteOnClose。属性一旦生效面板第一次 close 就被销毁下一次 instance() 拿到的指针就是悬空的。4.3 托盘程序的隐藏与还原托盘程序的核心逻辑是主窗口关闭时隐藏到托盘而不是退出托盘菜单点击时还原。MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { initTray(); // 创建 QSystemTrayIcon connect(m_tray, QSystemTrayIcon::activated, this, MainWindow::onTrayActivated); } void MainWindow::closeEvent(QCloseEvent *event) { if (m_reallyQuit) { // 真正退出标志 event-accept(); return; } hide(); // 关闭转隐藏 event-ignore(); } void MainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason) { if (reason QSystemTrayIcon::Trigger) { // 单击托盘图标 if (isVisible()) { hide(); } else { showNormal(); // 还原注意不是 show() raise(); activateWindow(); } } }这里三个细节都是踩坑换来的。第一必须有 m_reallyQuit 这样的标志否则从托盘菜单点退出时closeEvent 又会把窗口藏起来程序永远退不掉。第二还原用 showNormal() 而不是 show()防止之前处于最小化状态。第三必须配合 main 里的setQuitOnLastWindowClosed(false)否则窗口 hide 之后程序判定最后一个窗口已关闭直接退出托盘就是个摆设。我见过有人把托盘功能做出来测试时发现点关闭确实缩到托盘了但几秒后程序还是自己没了就是因为漏了 setQuitOnLastWindowClosed(false)。这个错误不好查因为现象和崩溃很像其实只是应用正常退出了。5. 模态与非模态窗口显示方式的选择与影响5.1 模态窗口到底模住了什么模态modal是个很实用的概念但很多人只记住了模态窗口会挡住后面的窗口没理解它真正的作用范围。模态的本质是限制事件分发。当一个模态窗口显示时QT会进入一个局部的嵌套事件循环把原本发给其他窗口的鼠标键盘事件拦下来只发给这个模态窗口以及它的子控件。这才是不管你怎么点后面窗口都没反应的真正原因不是简单的视觉遮挡。QT提供三个层级的模态Qt::ApplicationModal阻塞整个应用的所有窗口直到这个窗口关闭。适用于必须立即处理的提示、确认框。Qt::WindowModal只阻塞它的父窗口、所有祖先窗口以及它们的子窗口其他不相关的窗口不受影响。适用于某个文档窗口内部弹出的对话框。Qt::NonModal非模态不阻塞任何东西窗口独立存在。设置方式有两种一种是在构造或显示前setWindowModality(Qt::ApplicationModal)另一种是直接用exec()。对 QDialog 来说exec() 等价于设置了 ApplicationModal 并进入嵌套事件循环这也是为什么 exec() 会卡住在那一行直到对话框关闭。// 方式一显式设置模态属性再用 show QDialog dlg(this); dlg.setWindowModality(Qt::ApplicationModal); dlg.show(); // 后续代码继续执行 // 方式二直接用 exec阻塞等待 QDialog dlg(this); int ret dlg.exec(); // 这里会等待对话框关闭 if (ret QDialog::Accepted) { /* ... */ }两者的区别要记牢show() 不阻塞exec() 阻塞。如果你用 exec() 却期待后面的代码立刻执行那必然出错反过来用 show() 又想拿返回值也是拿不到的。我建议需要返回值、需要顺序执行的用 exec()只是展示信息、可以并行操作的用 show() 加模态属性。5.2 嵌套事件循环的副作用exec() 的便利是有代价的那就是嵌套事件循环。exec() 期间虽然调用它的那行代码停住了但整个程序的事件循环还在转意味着其他定时器可能还在触发其他窗口的信号槽可能还在响应用户在模态框打开期间做的操作可能通过信号影响到背后的逻辑这个特性会制造一些诡异的现象。比如你在 exec() 打开对话框之前启动了某个轮询定时器对话框开着的时候定时器依然在跑可能触发槽函数去改数据等对话框关闭返回时数据已经和你预期的不一样了。我在一个数据采集程序里就遇到过打开模态配置框修改采样参数期间后台定时器还在按旧参数存数据等配置确认返回发现已经多存了几条按旧参数的数据。解决办法是在打开模态框前停掉定时器关闭后再按新参数重启。凡是依赖当前状态的后台任务在进入模态嵌套循环前都要考虑暂停。还有一种情况是窗口在关闭时触发了另一个窗口的显示形成关闭即打开。如果不小心在这个链条里重入了同一个 exec()就会看到对话框叠了一堆。避免的办法是设置标志位防止重入或者把这类关闭后打开新窗口的逻辑从 closeEvent 里挪到更上层的调度逻辑里。6. 实操中真实踩到的窗口问题与排查技巧6.1 窗口关了内存不降的排查路径内存只增不减是窗口管理最典型的问题。我整理了一套排查顺序基本能覆盖九成情况。第一步确认窗口有没有被真正销毁。用 QPointer 或者 QObject::destroyed 信号来观察QPointerMyPanel tracker new MyPanel(); connect(tracker, QObject::destroyed, this, [](){ qDebug() panel destroyed; });如果关闭后一直没打印 panel destroyed说明对象生命周期没结束内存自然不会降。第二步检查是不是忘了设 WA_DeleteOnClose或者关闭逻辑走的是 hide 而不是 close。很多自定义关闭按钮里写的是this-hide()而不是this-close()那对象当然一直在。第三步如果确认销毁了但内存曲线还是不降那多半不是泄漏而是分配器不归还内存。现代内存分配器出于性能考虑释放的内存往往留在进程里备用不会立刻还给操作系统。用任务管理器看进程内存看到释放后不降很正常真正判断泄漏要看多次开合之后的趋势或者用专门的内存分析工具。这点我一开始也误解过白排查了半天。第四步如果确实是反复开合后阶梯式上涨那就找是不是有信号槽连接没断开、有数据容器只增不减、或者有对象挂在某个全局列表里没摘除。窗口销毁不代表它持有的数据被清理如果你的窗口在全局容器里注册过自己得在析构里注销。6.2 关闭二次触发导致崩溃双击关闭按钮程序崩了这种情况我遇到过不止一次。原因通常是关闭逻辑不幂等第一次 close 触发了销毁流程第二次 close 时对象已经析构访问它的成员就是野指针。解决办法有几个层次。最简单的是销毁后把指针置空前提是用 QPointer 或者你手动管理指针调用前判断if (m_panel) { m_panel-close(); m_panel nullptr; // 或者靠 QPointer 自动置空 }更根本的是区分请求关闭和已经关闭。可以借助 destroyed 信号统一清理connect(m_panel, QObject::destroyed, this, [this](){ m_panel nullptr; });这样不管窗口是怎么被销毁的手动删、父对象删、WA_DeleteOnClose指针都会被自动同步。用这个模式之后我基本没再遇到过二次关闭崩溃。6.3 常见问题速查表现象大概率原因处理方式窗口 hide 后程序自己退出最后一个窗口关闭触发退出且 quitOnLastWindowClosed 为 true设setQuitOnLastWindowClosed(false)show 调用了窗口不出现窗口还带最小化状态标志改用 showNormal() 或先清 windowState关闭确认框点取消窗口还是没了closeEvent 里没调event-ignore()补上 ignore关闭后指针失效导致崩溃对象已销毁而指针未置空用 QPointer 或监听 destroyed 同步置空模态框打开期间后台数据错乱嵌套事件循环里定时器仍在运行进入前停相关定时器退出后重启设置面板每次打开都是旧数据面板被复用但没刷新显示前调用刷新逻辑从配置同步托盘图标点了程序没反应还原用了 show() 而非 showNormal()或标志位没处理检查状态标志与调用方式反复开合内存阶梯上涨信号未断、数据未清、全局引用未注销在析构里做完整清理这张表我建议直接存下来出问题先对号入座能省下大量试错时间。6.4 几个不容易想到的避坑细节最后分享几个常规文档里不太提、但实操中很值钱的经验。窗口尺寸和位置的记忆。如果你希望复用的窗口每次出现在上次的位置和大小hide 之前不需要特别保存QT会给它留着。但如果窗口被销毁重建就得自己存 QSettings 或者配置文件在构造后 restoreGeometry。我在做多显示器适配时特别注意这点同一个窗口在副屏关闭、主屏打开时如果位置没做边界检查窗口可能出现在屏幕外用户直接看不到。显示前用QGuiApplication::screenAt()判断一下位置是否在某个屏幕范围内不在就居中。隐藏窗口的绘制暂停。一个隐藏的窗口不会收到 paintEvent所以它不会消耗绘制资源。但如果窗口里嵌了需要持续更新的图表或视频隐藏时虽然不绘制后台计算可能还在跑。做性能敏感的应用时我习惯在 hideEvent 里暂停这类后台计算showEvent 里恢复能省下可观的资源。父子窗口的焦点传递。父窗口 hide 的时候焦点会转移给谁是有规则的。如果父窗口管理不当可能出现焦点丢失、快捷键失灵。我处理这类问题的方式是在窗口显示后明确调用setFocus()给期望的控件不要依赖默认焦点分配。这个细节在做自定义控件和复杂表单时尤其重要。Qt::WA_DeleteOnClose 的连带效应。给一个有 parent 的子窗口设这个属性时要小心。窗口 close 被接受后触发 deleteLater对象被销毁父对象的子对象列表里也就移除了它。如果你的代码里还留着这个子窗口的裸指针用于别处访问下一次访问就是崩溃。用 QPointer 是最省心的防护。信号连接的清理时机。窗口销毁时QT会自动断开它作为发送者的所有连接也会断开指向它的接收者连接这部分是安全的。但如果连接用的是 lambda 且捕获了外部对象而外部对象的生命周期短于窗口lambda 里的捕获对象就成了悬空引用。这类问题不崩溃则已一崩就很难定位。我的习惯是不在长生命周期窗口的信号里捕获短生命周期对象的裸指针要么用 QPointer 捕获要么把连接挂在正确的上下文对象上利用 QT 的上下文自动断开机制。窗口的显示、隐藏和关闭看起来是QT里最基础的操作但它牵扯到对象生命周期、事件循环、内存管理和用户交互习惯这几条线随便一条没理清都可能变成线上问题。我自己的经验是与其出了问题一点点查不如在最开始就把窗口归谁管、什么时候销毁、关闭时怎么响应这三件事在设计阶段定死代码量不大省下的是后面反复的调试时间。
返回列表