跳转到内容
新建笔记

Qt 自定义信号:声明、连接与信号转发

信号声明某个对象发生了可供外部观察的变化;连接决定哪些函数接收这次通知。变化可以来自用户输入、定时器、数据计算或网络处理,并不只在点击界面时发生。本篇以 Qt 6 / C++17 实现一个完整的“发送者 → 转发者 → 接收者”例子。

信号、槽与 emit 分别负责什么

跳转到“信号、槽与 emit 分别负责什么”

signals: 中声明信号,信号函数的实现由 moc 生成,通常不手写函数体。emit changed(value) 发出通知;emit 本身不决定线程切换,也不把数据自动发送给任意对象,只有建立了适当连接的接收者才会处理它。

槽是可接收信号的函数。使用成员函数指针形式的 connect 时,接收目标可以是普通成员函数或 lambda,不必声明为 slots:。声明在 slots: 下的函数还可以通过元对象系统调用;直接按普通 C++ 方式调用槽时,仍服从 C++ 访问权限与调用规则。Qt 信号与槽

环节示例职责
声明void valueChanged(int value);定义通知的名称和参数
建立连接connect(sender, &Sender::valueChanged, receiver, &Receiver::setValue);将具体对象的通知关联到处理函数
发出emit valueChanged(value);将本次参数交给连接机制
接收void setValue(int value);处理数据,不要求反向知道发送者实现

一个信号可以连接多个目标,一个目标也可以接收多个来源。接收函数可以比信号少接收一些尾部参数,但接收的参数必须满足该连接形式的类型兼容规则,不能任意拼接不匹配的签名。

完整例子:信号直接连接另一个信号

跳转到“完整例子:信号直接连接另一个信号”

信号可以作为连接的接收目标。当 Source::valueChanged 发出时,Relay::valueChanged 会继续通知它自己的接收者。它适合封装内部组件:外部只关心转发层公开的接口。

将下面保存为 main.cpp:

#include <QCoreApplication>
#include <QObject>
#include <cassert>
class Source : public QObject {
Q_OBJECT
public:
void setValue(int value) {
if (value_ == value) return;
value_ = value;
emit valueChanged(value_);
}
signals:
void valueChanged(int value);
private:
int value_ = 0;
};
class Relay : public QObject {
Q_OBJECT
signals:
void valueChanged(int value);
};
class Receiver : public QObject {
public:
void accept(int value) { last = value; ++calls; }
int last = 0;
int calls = 0;
};
int main(int argc, char** argv) {
QCoreApplication app(argc, argv);
Source source;
Relay relay;
Receiver receiver;
const auto forward = QObject::connect(
&source, &Source::valueChanged, &relay, &Relay::valueChanged);
const auto handle = QObject::connect(
&relay, &Relay::valueChanged, &receiver, &Receiver::accept);
assert(forward && handle);
source.setValue(7);
assert(receiver.last == 7 && receiver.calls == 1);
source.setValue(7); // 状态未改变,不重复通知
assert(receiver.calls == 1);
QObject::disconnect(forward);
source.setValue(8);
assert(receiver.last == 7 && receiver.calls == 1);
}
#include "main.moc"

对应 CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(SignalRelay 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(signal-relay main.cpp)
target_link_libraries(signal-relay PRIVATE Qt6::Core)

本例对象都在同一线程,使用默认的自动连接,通知同步执行,所以无需启动事件循环即可断言结果。Receiver 没有声明新的元对象成员,使用普通成员函数接收;声明自定义信号的两个类均有 Q_OBJECT。本例在 Qt 6.10.2 实际编译并运行,断言通过。

参数、寿命与队列连接

跳转到“参数、寿命与队列连接”

如果连接使用队列投递,参数需要可按 Qt 元类型机制保存和传递,接收线程也需要可工作的事件循环。自定义类型可能需要 Q_DECLARE_METATYPE 和 qRegisterMetaType;不能把“C++ 函数允许这个参数”直接等同于“它能安全排队传递”。

指针参数即使能被投递,复制的通常也只是指针值,并不自动转移或延长所指对象寿命。lambda 捕获引用时,也必须确保捕获对象在调用期间仍然有效。带接收对象上下文的连接有利于管理断连和线程归属;详细例子见 连接方式、线程与生命周期。

避免形成无条件互相转发的信号环路,或在 setter 中无条件重复发出相同状态;直接连接可能立即递归。例子中的“值未改变就返回”是状态通知的基本保护,但不能代替对整个依赖图的设计。

这里整合了 信号可直接连接信号的原短笔记,并以可运行的转发和断连行为验证该结论。