前面所展示的那些非标准但仍然可用且非常实用的功能列表远未结束。如果我们把注意力完全集中在这些扩展上,甚至可以用它们写好几本书。可惜的是,我们目前只安排了一章来讨论这个话题。再将视线转向一些更为独特、更具“风味”的功能。
Qt 长期以来一直是一个用于构建 GUI 应用程序(而不仅仅是 GUI)的跨平台编程框架。自 1994 年诞生以来,Qt 经历了多次所有权变更,但在其波澜起伏的发展历程中不断成长,每一个版本都为 C++(以及其它语言)开发者带来了新的工具和特性。
然而,有一项核心功能几乎从未改变:信号/槽机制(Signals and Slots),以及支撑它的 Meta Object Compiler(MOC)。作为 Qt 的核心技术之一,MOC 能够将事件从一个组件(即“信号”)连接到另一个接收者(即“插槽”),从而实现模块之间的通信。
但这一强大功能的背后,是以牺牲部分标准 C++ 结构为代价的。为了使这种对象间的动态连接成为可能,Qt 引入了一些非标准的语法元素。例如:
下面是一段典型的 Qt 头文件示例,它使得后续代码能够成功编译并运行:
#ifndef MYCONTROL_H
#define MYCONTROL_H
#include <QObject>
#include <QPushButton>
#include <QWidget>
class MyControl : public QWidget {
Q_OBJECT
public:
MyControl(QWidget *parent = nullptr);
private slots:
void onButtonClicked();
signals:
void nameChanged(const QString &name);
private:
QPushButton *myButton;
};
#endif
那么问题来了:我们是否应该接受 Qt 提供的便利性,使用这套非常强大的信号/槽机制,即使必须编写非标准的 C++ 代码?还是应该坚持传统方式,通过纯标准 C++ 实现每个按钮和事件绑定,就像在 GTK 中所做的那样?
这个问题超出了本章所能回答的范围。最终的决定取决于项目的具体需求:包括目标环境的技术限制、项目利益相关者对平台的期望,以及开发团队自身的偏好和技术栈。
不过,请不要因为这些代码看起来不像是标准 C++ 就感到不安。尽管确实偏离了规范,但它们解决的是真实世界中的实际问题。在背后,隐藏着一套经过验证、优化并在多个大小项目中广泛使用的成熟实现机制,其已经经受住了时间的考验。
另一方面,Microsoft 对 C++ 的大规模扩展则采取了截然不同的路径。
C++/CLI 并不像 Qt 的 MOC 那样是一个特定于框架的工具,而是对 C++ 语言本身的扩展,专门用于支持 .NET 平台。Visual Studio 中用于 C++/CLI 的编译器(基于公共语言基础结构,而非传统的命令行接口)能够解析这些扩展语法,并生成有效的中间语言(MSIL)和本地代码。
下面是一段典型的 C++/CLI 示例代码。虽然内容简单 —— 只是拼接字符串数组并输出结果,但它展示了托管 C++ 的风格:
#include <iostream>
#include <atlstr.h>
#include <stdio.h>
using namespace System;
int main() {
array<String^>^ args = { "managed", "world" };
String^ s = "Hello";
for each (String ^ a in args) s += " " + a ;
CString cs(s);
wprintf(cs);
}
这段代码绝不是标准 C++。它看起来不像标准 C++,行为也不像,甚至语法也格格不入。它显然不属于 ISO C++ 的范畴。
那么,如果要用标准 C++ 来实现同样的功能,代码应该是这样的:
#include <array>
#include <iostream>
#include <string>
int main() {
std::array<std::string, 2> args = { "unmanaged", "world" };
std::string s = "Hello";
for(const auto& a : args) {
s += " " + a ;
}
std::cout << s;
}
这不仅更简洁、更现代,而且完全符合 C++ 标准。它是不是比前一种写法更清晰、更易维护呢?
未来,观察 C++ 托管扩展(如 C++/CLI)的发展将会是一件很有趣的事情。
目前,它充当了原生代码与托管代码之间的一座桥梁,但这是一个相对小众的领域。从长远来看,它的命运将取决于开发者社区的接受程度、是否有足够的生态系统支持,以及是否有其他技术(如 P/Invoke 或 COM Interop)逐步取代它所承担的角色。
毫无疑问,未来充满了变革。