文章

自定义信号和槽

信号链式传递是信号触发槽再发信号,实现多级响应;信号转发是接收信号后原样或处理后发出新信号,解耦模块通信。

自定义信号和槽

自定义信号和槽

信号链式传递

在 Qt 中,信号链式传递(Signal Chaining / Signal Relay)是指:

一个对象接收到信号后,在对应的槽函数中再次发出另一个信号,从而实现多个对象之间信号的逐级传递响应链式处理

这种模式可以让多个模块间解耦通信,适用于状态同步、事件广播、控制流分层等复杂场景。

基础示意图:

1
2
3
4
5
6
7
Object A  ---emit---> signalA()
                  |
                  v
Object B  <--slotA()--- 接收信号后 emit signalB()
                  |
                  v
Object C  <--slotB()--- 最终处理

自定义信号示例

widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget* parent = nullptr);
    ~Widget();

signals: // 信号默认强制规定为公有类型,这样才能保证其他对象能接收到信号
    void sendMsg(QString str); // 信号只需要声明,而不要写实体代码

public slots:
    void buttonClicked();

private:
    Ui::Widget* ui;
};
#endif // WIDGET_H

widget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "widget.h"
#include "./ui_widget.h"

Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
    // 关联按钮的点击事件到 buttonClicked
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::buttonClicked);
}

Widget::~Widget() {
    delete ui;
}

void Widget::buttonClicked(){
    // 用 emit 发信号
    emit sendMsg(tr("这是发送的信息"));
}

点击按钮后会发送一个自定义的信号 sendMsg(QString str)

showmsg.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef SHOWMSG_H
#define SHOWMSG_H

#include <QObject>

class ShowMsg : public QObject {
    Q_OBJECT
public:
    explicit ShowMsg(QObject* parent = nullptr);

public slots:
    void recvMsg(QString str);
};

#endif // SHOWMSG_H

showmsg.cpp

1
2
3
4
5
6
7
8
9
10
#include "showmsg.h"
#include <QMessageBox>

ShowMsg::ShowMsg(QObject* parent) : QObject{parent} {
}

void ShowMsg::recvMsg(QString str){
    // 第一个参数是父窗口指针,设置为 NULL,代表没有父窗口,就是在系统桌面直接弹窗
    QMessageBox::information(NULL, tr("Show"), str);
}

recvMsg(QString str) 负责接收信号并弹窗。

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "showmsg.h"
#include "widget.h"

#include <QApplication>

int main(int argc, char* argv[]) {
    QApplication a(argc, argv);
    Widget w;

    ShowMsg s;
    // 关联,信号里的字符串参数会自动传递给槽函数
    // 在 main 函数里,需要手动加 QObject:: 前缀来调用 connect 函数
    QObject::connect(&w, &Widget::sendMsg, &s, &ShowMsg::recvMsg);

    w.show();
    return a.exec();
}

信号转发

在 Qt 中,信号转发(Signal Forwarding)是指:

一个对象接收到信号后,不做任何业务逻辑处理,而是原样或稍作处理后再次发出一个信号,从而把事件“转发”给其他对象或上层模块。

这是一种常用的“事件中继机制”,能让对象之间保持良好的解耦

方式一:信号直接转发信号(推荐)

1
connect(sender, &Sender::signalA, this, &ThisClass::signalB);

要求:signalAsignalB 的参数 完全一致

方式二:槽函数中转发出另一个信号(可以做参数处理)

如果想对信号参数稍作处理,必须手写槽函数,再用 emit 发出新信号。

1
2
3
4
5
6
7
8
9
10
11
class Forwarder : public QObject {
    Q_OBJECT
public slots:
    void onSignalA(int value) {
        // 做一点处理,然后发出另一个信号
        emit signalB(value + 1);
    }

signals:
    void signalB(int value);
};

方式三:lambda 转发(用于临时逻辑、轻量场景)

在 C++11 及以上,可以用 lambda 捕获参数,转发出去:

1
2
3
QObject::connect(&objA, &A::signalA, [&objB](int v) {
    emit objB.signalB(v);  // 假设 signalB 是 public 信号
});

信号直接转发信号示例

widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

public:
    Widget(QWidget* parent = nullptr);
    ~Widget();

signals:
    void sendVoid(); // 没有参数,所以能和按钮的 clicked 信号匹配,实现信号到信号的关联

private:
    Ui::Widget* ui;
};
#endif // WIDGET_H

widget.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include "widget.h"
#include "./ui_widget.h"

Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
    ui->setupUi(this);
    // 第四个参数是信号,直接关联到自定义的信号,而不需要槽函数中转
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::sendVoid);
}

Widget::~Widget() {
    delete ui;
}

showvoid.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef SHOWVOID_H
#define SHOWVOID_H

#include <QObject>

class ShowVoid : public QObject {
    Q_OBJECT
public:
    explicit ShowVoid(QObject* parent = nullptr);

signals:

public slots:
    void recvVoid();
};

#endif // SHOWVOID_H

showvoid.cpp

1
2
3
4
5
6
7
8
9
#include "showvoid.h"
#include <QMessageBox>

ShowVoid::ShowVoid(QObject* parent) : QObject{parent} {
}

void ShowVoid::recvVoid(){
    QMessageBox::information(NULL, tr("Show"), tr("Just void."));
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "showvoid.h"
#include "widget.h"

#include <QApplication>

int main(int argc, char* argv[]) {
    QApplication a(argc, argv);
    Widget w;

    ShowVoid s;
    // clicked 发给 sendVoid,sendVoid 再发给 recvVoid
    QObject::connect(&w, &Widget::sendVoid, &s, &ShowVoid::recvVoid);

    w.show();
    return a.exec();
}

信号参数推荐用传值

官方推荐

Qt 官方推荐信号的参数使用按值传递(by value),特别是对于 Qt 自己的类如 QStringQDateTime 等。

原因: 这些 Qt 类型是 Implicitly Shared Classes(隐式共享),即“写时拷贝(COW)”。按值传递不会造成实际复制,除非修改了对象内容。

官方解释(翻译简述)

对于信号参数,按值传递会更稳妥,因为 Qt 的元对象系统内部会复制参数。即使你传引用,也会在底层被复制成一个值。

出处(Qt 6 官方文档):

“All signal arguments are stored in a QVariant and passed by value internally, so there is no real performance benefit in passing by reference for signal arguments.” —— Qt Object Model

信号参数推荐按值传的核心理由

原因
元对象系统Qt 会在内部复制信号参数,不管你传的是引用
隐式共享QString, QList, QMap 等按值传递开销非常低
可移植性有些连接方式(Queued)要求参数必须可序列化(by value)
一致性信号一般不修改参数,按值更简洁清晰

所以结论是:

类型信号中参数写法推荐理由
QStringQString name隐式共享,元系统复制,无性能差
int 等基础类型int index本来就是值传递
自定义类MyStruct value(如果是轻量的)避免引用生命周期问题

槽函数则不同

  • 槽函数更常使用 const QString& 以避免复制。
  • 因为槽函数是你直接调用的,不经过元对象系统,可以利用引用性能优势。
1
2
3
4
5
6
7
// 信号推荐写法(按值)
signals:
    void projectSettingChanged(QString name, QString path);

// 槽函数推荐写法(按引用)
public slots:
    void onProjectSettingChanged(const QString& name, const QString& path);
本文由作者按照 CC BY 4.0 进行授权