槽函数首先是 C++ 函数。它可以由连接触发,也可以像普通成员函数一样直接调用;写上 slots 不会自动创建线程、异步执行或消除数据竞争。要理解一次调用,分别看连接的签名、连接类型和接收对象的生命周期。
slots 声明与现代 connect
跳转到“slots 声明与现代 connect”带 Q_OBJECT 的类把函数声明在 public slots: 等区域后,moc 会把槽登记到元对象系统。现代成员函数指针形式的 connect 也能连接普通成员函数,不要求接收函数必须写在 slots: 下;lambda 则可以完成参数变换和上下文绑定。
信号表示对象对外发出的通知,例如 QPushButton::clicked、QTimer::timeout、QTcpSocket::readyRead。事件处理函数如 QWidget::mousePressEvent 是虚函数,不是信号,不能用 connect(widget, &QWidget::mousePressEvent, …) 把它当作公开信号接出。要观察输入,应重写处理函数或安装事件过滤器,再在需要时发出自己的业务信号。参见 [[10-knowledge/computing/software-development/application-development/desktop/qt/events/event-dispatch-and-filters|事件分发与过滤器]]。Qt:信号与槽
参数的来源和匹配规则
跳转到“参数的来源和匹配规则”| 情况 | 正确理解 |
|---|---|
| 信号携带参数 | 参数由发射信号的一方提供;槽接收与其签名兼容的参数 |
| 槽参数较少 | 可以忽略信号尾部多余的参数;接收的前缀类型仍须匹配 |
| 无参数槽 | 可以连接有参数信号,舍弃信号参数 |
| 带默认值的槽 | 直接 C++ 调用可以省略实参;现代函数指针连接不能因此补齐信号缺失的参数 |
| 参数重排、常量或附加上下文 | 用 lambda 显式适配,例如调用 receiver.setValue(42) |
| 重载槽 | 用 qOverload<…> 或明确的成员函数指针类型选择重载 |
| 对象指针或引用 | 信号必须实际携带它,或由 lambda 明确捕获;不会自动获得发送者参数 |
| 变参数量 | 不把 C 风格 ... 当作任意参数槽接口;使用明确的数据结构、容器或固定签名 |
旧的字符串形式 SIGNAL/SLOT 对已登记的带默认参数槽存在特殊支持;这不适用于现代函数指针写法。现代写法通常能在编译期检查参数兼容性,并支持适当的隐式转换;字符串写法的检查时机和能力不同,不宜混用其结论。Qt:两种连接语法与默认参数
完整验证:少参数、默认参数适配、重载和排队调用
跳转到“完整验证:少参数、默认参数适配、重载和排队调用”以下程序使用同一线程中的两个对象。它证明普通成员函数也能接收信号,默认参数需要由 lambda 发起普通调用,以及 QueuedConnection 即使在同一线程也会延后执行。程序用断言检查结果后退出。
#include <QCoreApplication>#include <QEvent>#include <QObject>#include <QPointer>#include <QString>#include <cassert>
class Sender final : public QObject { Q_OBJECTsignals: void changed(int value, const QString &label); void ping(); void number(int value); void objectReady(QObject *object);};
class Receiver final : public QObject { Q_OBJECTpublic: int value = 0; int notifications = 0; int defaultValue = 0; int overloadValue = 0; int queuedValue = 0; QPointer<QObject> observed; void setValue(int next) { value = next; } // 普通成员函数 void count() { ++notifications; } void overloaded(int next) { overloadValue = next; } void overloaded(const QString &text) { overloadValue = text.size(); } void setQueued(int next) { queuedValue = next; }public slots: void withDefault(int next = 42) { defaultValue = next; }};
int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Sender sender; Receiver receiver; QObject::connect(&sender, &Sender::changed, &receiver, &Receiver::setValue); QObject::connect(&sender, &Sender::changed, &receiver, &Receiver::count); QObject::connect(&sender, &Sender::ping, &receiver, [&receiver] { receiver.withDefault(); }); QObject::connect(&sender, &Sender::number, &receiver, qOverload<int>(&Receiver::overloaded)); QObject::connect(&sender, &Sender::number, &receiver, &Receiver::setQueued, Qt::QueuedConnection); QObject::connect(&sender, &Sender::objectReady, &receiver, [&receiver](QObject *object) { receiver.observed = object; });
emit sender.changed(7, QStringLiteral("sample")); assert(receiver.value == 7 && receiver.notifications == 1); emit sender.ping(); assert(receiver.defaultValue == 42); receiver.withDefault(9); // 普通调用,不经过连接分发 assert(receiver.defaultValue == 9); emit sender.number(11); assert(receiver.overloadValue == 11); assert(receiver.queuedValue == 0); QCoreApplication::sendPostedEvents(&receiver, QEvent::MetaCall); assert(receiver.queuedValue == 11); { QObject payload; emit sender.objectReady(&payload); assert(receiver.observed == &payload); } assert(receiver.observed.isNull());}#include "main.moc"保存为 main.cpp 并使用以下构建文件。Q_OBJECT 在源文件内,因此示例包含 main.moc 并启用 AUTOMOC。
cmake_minimum_required(VERSION 3.20)project(SlotArguments LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_AUTOMOC ON)find_package(Qt6 REQUIRED COMPONENTS Core)add_executable(slot_arguments main.cpp)target_link_libraries(slot_arguments PRIVATE Qt6::Core)使用 Debug 构建保留断言。sendPostedEvents 只是这个短测试中确定性地处理接收者的排队调用;正常应用由接收线程的事件循环处理,不应为此不断手工刷新事件队列。
连接类型决定何时、在哪个线程调用
跳转到“连接类型决定何时、在哪个线程调用”DirectConnection 在发射信号的调用线程立即执行接收函数。QueuedConnection 复制所需参数并投递调用,之后由接收对象所在的线程处理。AutoConnection 在发射时根据当前发射线程与接收对象的线程归属选择,不能只比较发送对象和接收对象各自的 thread()。
所以,GUI 对象的耗时槽仍然会阻塞 GUI;把它声明为槽不会解决这一点。后台计算应采用合适的工作对象或任务机制,再用连接把结果传回界面。直接跨线程调用普通成员函数也不会自动切换到接收者线程。进一步见 [[10-knowledge/computing/software-development/application-development/desktop/qt/signals-slots/connections|连接与生命周期]] 和 [[10-knowledge/computing/software-development/application-development/desktop/qt/concurrency/qthread|线程选型]]。
排队参数必须满足 Qt 对相关类型的复制与元类型要求。复制一个裸指针并不会延长它指向对象的生命周期;跨线程更不能把 QPointer 当成任意访问对象的锁。上面的指针示例明确在同一线程同步执行,仅演示销毁后观察指针清空。实际连接中的 lambda 还应提供合适的上下文对象,并避免捕获早于连接失效的局部引用。Qt:QObject 的线程归属和连接