第18.8章 Qt、MFC,以及为什么 GUI 是 C++ 走歪的地方

English

1. 最被滥用的领域里最被滥用的特性

virtual 和 new 是 C++ 里被滥用得最厉害的特性。 它们被滥用得最厉害的地方是图形用户界面, 而把这件事正常化的两个库就是 Qt 和 MFC。 如果你学 C++ 是为了写桌面应用,你最先被教的几乎肯定是这种风格, 而本章存在的意义就是反对它。

原因是历史性的,而且在 1992 年是合理的:窗口是一种行为随种类变化的东西, 事件从外面到来,最自然的建模工具就是一个带虚事件处理器的基类, 再加上 new,因为控件的寿命比创建它的函数长。 三十年之后,这个决定的代价看得见了,而替代方案(Web 前端加原生服务)当时还不存在。

2. Qt 具体做了什么

把光鲜的部分剥掉,Qt 就是这个:

这些都不是无能。这就是一个 1995 年的设计被维护到 2026 年的代价, 而语言至今仍然表达不出“一列回调和一枚类型标签”。

3. MFC 具体做了什么

MFC 是这门语言里最清晰的警示故事:它发明了自己的 RTTI (CRuntimeClass)、自己的分派(消息映射)、自己的拥有模型, 每一个都是为了绕开 C++ 对象模型的某一项代价, 而每一个都变成了永久的、不可移植的、不可测试的一层。

4. 账单

代价 来自哪里
二进制在你写下第一行代码之前就有几十 MB 框架连同它所有控件、资源、元对象,以及每个虚函数(不管调没调用) 一起被静态链接进来(第18.4章)
每个控件一次堆对象,再加一棵父树来释放它们 每个构造函数里一个 new,拥有关系被推进基类
一个 ABI、一个编译器、一套选项,永远 带虚接口的、非 header-only 的 C++
两套类型系统:C++ 的和框架的 moc/staticMetaObject 与 CRuntimeClass
不可测的 UI 逻辑 逻辑住在需要窗口、设备和消息泵的虚处理器里
UI 不重新发一个二进制就没法更新 界面被编译进程序里

5. 我们改做什么:PWA 加原生服务

沿着一条和 C++ 毫无关系的线,把程序切成两半:

这不是妥协,在本章测量过的每一条轴上它都严格更好: 没有每个控件一次 new、没有虚层次、没有 RTTI、 没有框架 ABI、没有额外的类型系统,而且界面独立于二进制发版。 第21章讲的是把后半部分 —— 服务 —— 做成一个能安装、能离线工作的 PWA (第21章 PWA)。

如果你确实需要一个原生窗口,就直接用平台 API (Windows 上的 CreateWindowEx 加消息循环,Linux 上的 Wayland/X11),放在你自己的薄包装后面。那是几百行, 没有对象模型、没有 moc、没有第二套 RTTI, 而 fast_io 已经给了你驱动它所需的文件句柄和 io_uring/IOCP 管道 (第11.14章)。

6. 经验法则