Ch18.8: Qt, MFC, and Why GUIs Are Where C++ Went Wrong

中文(简体)

1. The Most Overused Feature in the Most Overused Domain

virtual and new are the most overused features in C++. The place where they are most overused is the graphical user interface, and the two libraries that did the most to normalise it are Qt and MFC. If you learn C++ to write desktop applications, this is almost certainly the style you will be taught first, and it is the style this chapter exists to argue against.

The reason is historical, and it was reasonable in 1992: a window is a thing whose behaviour varies by kind, events arrive from outside, and the natural modelling tool is a base class with virtual event handlers, plus new because widgets outlive the function that created them. Three decades later the cost of that decision is visible, and the alternative (a web front end talking to a native service) did not exist yet.

2. What Qt Does, Concretely

Strip away the polish and Qt is this:

None of that is incompetence. It is what a 1995 design costs when it is maintained into 2026 and the language still cannot express "a list of callbacks and a type tag".

3. What MFC Does, Concretely

MFC is the clearest cautionary tale in the language: it invented its own RTTI (CRuntimeClass), its own dispatch (message maps) and its own ownership model, each to work around a cost of the C++ object model, and each became a permanent, non-portable, un-testable layer.

4. The Bill

Cost Where it comes from
binary size: tens of MB before your first line of code the framework is statically linked with all its widgets, resources, meta-objects and every virtual function, called or not (Ch18.4)
one heap object per widget, and a parent tree to free them new in every constructor, ownership pushed into the base class
one ABI, one compiler, one set of flags, forever non-header-only C++ with virtual interfaces
two type systems: C++'s and the framework's moc/staticMetaObject and CRuntimeClass
untestable UI logic logic lives in virtual handlers that need a window, a device and a pump
a UI you cannot update without shipping a new binary the interface is compiled into the program

5. What We Do Instead: A PWA and a Native Service

Split the program in two along a line that has nothing to do with C++:

This is not a compromise, it is strictly better on every axis this chapter has measured: no new per widget, no virtual hierarchy, no RTTI, no framework ABI, no extra type system, and the interface ships independently of the binary. Chapter 21 is about making the second half — the service — a PWA that installs and works offline (Ch21: PWAs).

If you genuinely need a native window, use the platform API directly (CreateWindowEx and a message loop on Windows, Wayland/X11 on Linux) behind your own thin wrapper. It is a few hundred lines, it has no object model, no moc, no second RTTI, and fast_io already gives you the file handles and the io_uring/IOCP plumbing to drive it (Ch11.14).

6. Rules of Thumb