跳转到内容
新建笔记

Qt 槽函数:参数匹配、普通调用与执行线程

槽函数首先是 C++ 函数。它可以由连接触发,也可以像普通成员函数一样直接调用;写上 slots 不会自动创建线程、异步执行或消除数据竞争。要理解一次调用,分别看连接的签名、连接类型和接收对象的生命周期。

带 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_OBJECT
signals:
void changed(int value, const QString &label);
void ping();
void number(int value);
void objectReady(QObject *object);
};
class Receiver final : public QObject {
Q_OBJECT
public:
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 的线程归属和连接