Qt 的 connect 建立发送者信号与接收者函数之间的关系。连接本身不会创建线程;调用在哪个线程发生,由连接类型、发出信号时的执行线程和接收者的线程归属决定。
本页面向 Qt 6、C++17。优先使用成员函数指针或带上下文对象的 lambda,让编译器检查参数,并让对象销毁时自动断开连接。
C++ 连接的基本规则
跳转到“C++ 连接的基本规则”connect(button, &QPushButton::clicked, this, &MainWindow::close);connect(button, &QPushButton::clicked, this, [this] { close(); });上面是已有 button 和 MainWindow 的成员代码片段。第二种连接中的 this 同时作为上下文和捕获指针:上下文控制接收线程和断开时机,捕获使函数体能够访问成员;二者作用不同。
槽可以接收信号参数的前几个,数量不能更多;函数指针连接允许满足要求的类型转换。旧 SIGNAL(...) / SLOT(...) 字符串连接在运行时核对签名,槽须出现在元对象系统中;不要混用字符串信号与 lambda,也不要把字符串检查当成编译期检查。QObject::connect
完整例子:成员连接、去重和 lambda 寿命
跳转到“完整例子:成员连接、去重和 lambda 寿命”// main.cpp;Qt6::Core,CMAKE_AUTOMOC=ON#include <QCoreApplication>#include <QObject>#include <QString>#include <cassert>
class Source : public QObject { Q_OBJECTpublic: void send(int value) { emit dataReady(value, QStringLiteral("sample")); }signals: void dataReady(int value, const QString &label);};
class Receiver : public QObject { Q_OBJECTpublic: int last = 0;public slots: void accept(int value) { last = value; }};
int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Source source; Receiver receiver; const auto first = QObject::connect(&source, &Source::dataReady, &receiver, &Receiver::accept, Qt::UniqueConnection); const auto duplicate = QObject::connect(&source, &Source::dataReady, &receiver, &Receiver::accept, Qt::UniqueConnection); assert(first); assert(!duplicate); source.send(7); assert(receiver.last == 7); // 多余的 label 参数没有交给 accept QObject::disconnect(first);
int calls = 0; { QObject context; QObject::connect(&source, &Source::dataReady, &context, [&calls](int) { ++calls; }, Qt::QueuedConnection); source.send(8); assert(calls == 0); QCoreApplication::processEvents(); // 本例仅用于检查队列投递 assert(calls == 1); source.send(9); // 先入队,再销毁上下文 } QCoreApplication::processEvents(); assert(calls == 1); // 已销毁的 context 不会再执行排队的 lambda}
#include "main.moc"普通应用应运行事件循环,而不是不断 processEvents 来维持界面。该例中的手动处理仅为了验证投递时机。
Qt::UniqueConnection 能去重匹配的成员函数连接,不能用来去重任意 lambda、自由函数或函数对象。需要管理这些连接时,保存 QMetaObject::Connection 并在适当时机 disconnect。上下文销毁会断连,但不会延长 lambda 捕获的其他裸指针或引用的寿命。QObject 连接规则
连接类型与线程
跳转到“连接类型与线程”| 类型 | 槽在哪执行 | 调用方是否等待 |
|---|---|---|
| AutoConnection | 发出信号的当前线程与接收者所属线程相同则直接,否则排队 | 取决于实际选择 |
| DirectConnection | 当前发出信号的执行线程 | 同步调用直到槽返回 |
| QueuedConnection | 接收者所属线程的事件循环 | 发送方不等待槽执行 |
| BlockingQueuedConnection | 接收者所属线程 | 发送方等待槽完成 |
判断 AutoConnection 时不是比较两个 QObject 的 thread() 就结束了;sender 对象的归属可能不同于正在发出信号的线程。BlockingQueuedConnection 用于同一线程会死锁,跨线程也需考虑循环等待,不能作为默认同步办法。
队列连接复制参数,需要相关类型能被 Qt 元类型系统处理;接收线程必须能处理事件。DirectConnection 不会为了界面安全而自动切回 GUI 线程。详见 Qt 连接类型、Worker Object。
多个按钮传递各自标识
跳转到“多个按钮传递各自标识”通常用带上下文的 lambda 捕获每个按钮的标识最直接。需要维护旧 QSignalMapper 代码时,Qt 6 使用 mappedInt、mappedString、mappedObject,不能照搬旧版 mapped(...) 和 QWidget 专用重载。
// Qt 6 片段:button1、button2、receiver 已创建auto *mapper = new QSignalMapper(receiver);mapper->setMapping(button1, QStringLiteral("first"));mapper->setMapping(button2, QStringLiteral("second"));QObject::connect(button1, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));QObject::connect(button2, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));QObject::connect(mapper, &QSignalMapper::mappedString, receiver, [](const QString &name) { qDebug() << name; });如果只是给一个按钮绑定常量,可改成 connect(button1, &QPushButton::clicked, receiver, [name] { /* 使用 name */ });,无需 mapper。QSignalMapper
自动关联不是只靠函数名猜测
跳转到“自动关联不是只靠函数名猜测”on_<objectName>_<signalName>() 需要元对象可见的槽、对应子对象的 objectName,以及一次 QMetaObject::connectSlotsByName(receiver)。由 uic 生成的 setupUi() 通常会执行该调用;手动创建并命名的控件应在相应自动关联调用之前存在。
不要既让 setupUi 自动关联同一个槽,又手动重复 connect,否则一次点击可能执行两次。新增控件后需要补连接时,显式 connect 更容易审查。connectSlotsByName
C++ 与 QML 要显式选择实例
跳转到“C++ 与 QML 要显式选择实例”C++ 暴露给 QML 的信号和可调用函数应属于带 Q_OBJECT 的 QObject 派生类;QML 可调用 public slots 或 Q_INVOKABLE 方法。下面是接口片段,实际项目需把它注册为 QML 类型、单例或向引擎暴露一个有效实例:
class Backend : public QObject { Q_OBJECTpublic: using QObject::QObject; Q_INVOKABLE void submit(const QString &text) { emit sendData(text); }signals: void sendData(const QString &text);};在已有 backend 实例的页面中,用 Connections 指定发送者:
import QtQuick
Item { id: page property string received: "" Connections { target: backend function onSendData(text) { page.received = text } } Component.onCompleted: backend.submit("hello")}若注册的是可创建类型 Backend,可以在 QML 中实例化 Backend { onSendData: ... };这个处理器只接收该实例的信号,不会自动接收同类型另一个对象发出的信号。向 QML 暴露 C++ 属性与方法
两个 QML 对象之间也需要实际连接。以下是可独立加载的示例:
import QtQuick
Item { id: page property string received: "" QtObject { id: source signal sendData(string text) } Connections { target: source function onSendData(text) { page.received = text } } Component.onCompleted: source.sendData("connected")}在另一文件里单独声明 onSendData,并不会自动订阅 source。也可以调用具体信号对象的 connect 方法,但要同样管理实例和回调寿命。QML Connections
从 C++ 查找 QML 对象时使用 objectName,不要把 QML 的 id 当作 findChild() 的名字。调用 QML 方法可使用 QMetaObject::invokeMethod,检查查找结果和返回值;QML 信号也能连接到 C++ 槽。跨越 QML/C++ 边界时,优先设计明确的属性和接口,避免业务代码依赖深层视图对象树。从 C++ 与 QML 对象交互
lambda 捕获速查
跳转到“lambda 捕获速查”在本页 C++17 语境下,[capture](parameters) mutable -> ReturnType { body } 描述捕获、参数、可修改的值捕获、返回类型与函数体。
| 捕获 | 含义 |
|---|---|
[value] | 保存 value 的副本 |
[&value] | 保存引用,要求原变量仍存在 |
[this] | 保存当前对象指针,不复制整个对象 |
[=, &a] | 默认按值捕获使用到的变量,a 按引用 |
[&, a] | 默认按引用,a 按值 |
mutable 允许修改闭包中按值捕获的副本,不会自动修改原变量;C++17 中此时仍需写参数括号。不能重复指定同一种默认捕获,例如 [=, a];this 也不能写成 &this。异步连接中避免随意 [&] 捕获短寿命局部变量,优先只捕获实际需要的数据。