跳转到内容
新建笔记

Qt 信号连接:线程、生命周期与 C++ / QML 交互

Qt 的 connect 建立发送者信号与接收者函数之间的关系。连接本身不会创建线程;调用在哪个线程发生,由连接类型、发出信号时的执行线程和接收者的线程归属决定。

本页面向 Qt 6、C++17。优先使用成员函数指针或带上下文对象的 lambda,让编译器检查参数,并让对象销毁时自动断开连接。

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_OBJECT
public:
void send(int value) { emit dataReady(value, QStringLiteral("sample")); }
signals:
void dataReady(int value, const QString &label);
};
class Receiver : public QObject {
Q_OBJECT
public:
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_OBJECT
public:
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 对象交互

在本页 C++17 语境下,[capture](parameters) mutable -> ReturnType { body } 描述捕获、参数、可修改的值捕获、返回类型与函数体。

捕获含义
[value]保存 value 的副本
[&value]保存引用,要求原变量仍存在
[this]保存当前对象指针,不复制整个对象
[=, &a]默认按值捕获使用到的变量,a 按引用
[&, a]默认按引用,a 按值

mutable 允许修改闭包中按值捕获的副本,不会自动修改原变量;C++17 中此时仍需写参数括号。不能重复指定同一种默认捕获,例如 [=, a];this 也不能写成 &this。异步连接中避免随意 [&] 捕获短寿命局部变量,优先只捕获实际需要的数据。