Qt 信号与槽深入 #

信号与槽基础回顾 #

基本概念 #

text
┌─────────────────────────────────────────────────────────────┐
│                    信号与槽模型                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────┐                    ┌─────────────┐       │
│   │   发送者    │                    │   接收者    │       │
│   │             │                    │             │       │
│   │  ┌───────┐  │     信号发射       │  ┌───────┐  │       │
│   │  │ 信号  │──┼────────────────────┼─>│  槽   │  │       │
│   │  └───────┘  │                    │  └───────┘  │       │
│   │             │                    │             │       │
│   └─────────────┘                    └─────────────┘       │
│                                                             │
│   特点:                                                    │
│   - 信号:对象状态改变时发射                                 │
│   - 槽:响应信号的函数                                      │
│   - 连接:建立信号与槽的关联                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

定义信号和槽 #

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

signals:
    void simpleSignal();
    void signalWithInt(int value);
    void signalWithString(const QString &text);
    void signalWithMultiple(int a, const QString &b, double c);
};

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

public slots:
    void onSimpleSignal();
    void onSignalWithInt(int value);
    void onSignalWithString(const QString &text);

private slots:
    void onInternalSignal();

    void anyMemberFunction(int value);  // Qt 5+ 任意成员函数
};

连接方式详解 #

Qt 5+ 函数指针语法(推荐) #

cpp
Sender *sender = new Sender;
Receiver *receiver = new Receiver;

// 基本连接
connect(sender, &Sender::simpleSignal,
        receiver, &Receiver::onSimpleSignal);

// 带参数的信号
connect(sender, &Sender::signalWithInt,
        receiver, &Receiver::onSignalWithInt);

// 连接到任意成员函数
connect(sender, &Sender::signalWithInt,
        receiver, &Receiver::anyMemberFunction);

// 连接到 Lambda
connect(sender, &Sender::signalWithInt, [](int value) {
    qDebug() << "Lambda received:" << value;
});

// 连接到 Lambda(捕获上下文)
connect(sender, &Sender::signalWithInt, [receiver](int value) {
    receiver->someMethod(value);
});

// 连接到普通函数
void globalFunction(int value) {
    qDebug() << "Global function:" << value;
}
connect(sender, &Sender::signalWithInt, globalFunction);

处理信号重载 #

cpp
class MyObject : public QObject
{
    Q_OBJECT
signals:
    void valueChanged(int value);
    void valueChanged(const QString &value);
};

// 使用函数指针区分重载
connect(obj, QOverload<int>::of(&MyObject::valueChanged),
        [](int value) { qDebug() << "Int:" << value; });

connect(obj, QOverload<const QString&>::of(&MyObject::valueChanged),
        [](const QString &value) { qDebug() << "String:" << value; });

Qt 旧式语法(不推荐) #

cpp
// 旧式语法(运行时检查)
connect(sender, SIGNAL(signalWithInt(int)),
        receiver, SLOT(onSignalWithInt(int)));

// 问题:
// ❌ 编译时不检查参数类型
// ❌ 参数不匹配时运行时才报错
// ❌ 重构困难

连接类型 #

连接类型枚举 #

cpp
// 自动连接(默认)
// 根据发送者和接收者是否在同一线程决定连接类型
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::AutoConnection);

// 直接连接
// 槽函数在信号发射时立即执行(同一线程)
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::DirectConnection);

// 队列连接
// 槽函数在接收者所在线程的事件循环中执行
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::QueuedConnection);

// 阻塞队列连接
// 发送者等待槽函数执行完成(跨线程)
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::BlockingQueuedConnection);

// 唯一连接
// 确保连接只建立一次
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::UniqueConnection);

连接类型选择 #

text
┌─────────────────────────────────────────────────────────────┐
│                    连接类型选择指南                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  发送者和接收者在同一线程:                                   │
│  └── 使用 AutoConnection(默认)或 DirectConnection         │
│                                                             │
│  发送者和接收者在不同线程:                                   │
│  ├── 需要异步处理:QueuedConnection                         │
│  ├── 需要同步等待:BlockingQueuedConnection                 │
│  └── 一般情况:AutoConnection(自动选择队列连接)            │
│                                                             │
│  避免重复连接:                                              │
│  └── 使用 UniqueConnection                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

跨线程信号槽 #

工作线程示例 #

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

public slots:
    void doWork(int parameter) {
        // 在工作线程中执行
        for (int i = 0; i < 100; ++i) {
            emit progressChanged(i);
            QThread::msleep(50);
        }
        emit workFinished();
    }

signals:
    void progressChanged(int percent);
    void workFinished();
};

class Controller : public QObject
{
    Q_OBJECT
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::startWork, worker, &Worker::doWork);
        connect(worker, &Worker::progressChanged, this, &Controller::onProgress);
        connect(worker, &Worker::workFinished, this, &Controller::onFinished);
        
        workerThread.start();
    }

    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }

signals:
    void startWork(int parameter);

private slots:
    void onProgress(int percent) {
        qDebug() << "Progress:" << percent;
    }
    
    void onFinished() {
        qDebug() << "Work finished";
    }

private:
    QThread workerThread;
};

线程安全注意事项 #

cpp
// 跨线程访问数据需要同步
class SharedData : public QObject
{
    Q_OBJECT
public:
    void setData(int value) {
        QMutexLocker locker(&m_mutex);
        m_data = value;
    }
    
    int data() const {
        QMutexLocker locker(&m_mutex);
        return m_data;
    }

private:
    mutable QMutex m_mutex;
    int m_data = 0;
};

// 使用信号槽跨线程传递数据(线程安全)
connect(sender, &Sender::dataReady, receiver, &Receiver::processData,
        Qt::QueuedConnection);  // 数据会被复制

发射信号 #

emit 关键字 #

cpp
class Counter : public QObject
{
    Q_OBJECT
public:
    int value() const { return m_value; }
    
    void setValue(int value) {
        if (m_value != value) {
            m_value = value;
            emit valueChanged(value);  // 发射信号
        }
    }

signals:
    void valueChanged(int newValue);

private:
    int m_value = 0;
};

直接调用信号 #

cpp
// Qt 5+ 可以直接调用信号(不使用 emit)
sender->signalWithInt(42);

// 通过元对象系统调用
QMetaObject::invokeMethod(sender, "signalWithInt", Q_ARG(int, 42));

断开连接 #

断开方式 #

cpp
// 断开特定连接
QMetaObject::Connection conn = connect(sender, &Sender::signal, receiver, &Receiver::slot);
disconnect(conn);

// 断开发送者和接收者之间的所有连接
disconnect(sender, nullptr, receiver, nullptr);

// 断开发送者的特定信号
disconnect(sender, &Sender::signal, nullptr, nullptr);

// 断开接收者的特定槽
disconnect(nullptr, nullptr, receiver, &Receiver::slot);

// 断开特定信号和槽的连接
disconnect(sender, &Sender::signal, receiver, &Receiver::slot);

高级用法 #

信号转信号 #

cpp
class Relay : public QObject
{
    Q_OBJECT
public:
    Relay(QObject *parent = nullptr) : QObject(parent) {
        connect(this, &Relay::inputSignal, this, &Relay::outputSignal);
    }

signals:
    void inputSignal(int value);
    void outputSignal(int value);
};

Lambda 捕获注意事项 #

cpp
// 捕获 this(注意对象生命周期)
connect(sender, &Sender::signal, this, [this](int value) {
    this->processValue(value);  // 如果 this 被删除会崩溃
});

// 使用 QPointer 安全捕获
QPointer<QObject> safePtr = this;
connect(sender, &Sender::signal, [safePtr](int value) {
    if (safePtr) {
        safePtr->processValue(value);
    }
});

// 使用上下文对象(Qt 5.10+)
connect(sender, &Sender::signal, this, [this](int value) {
    this->processValue(value);
});  // this 被删除时自动断开连接

延迟调用 #

cpp
// 使用 QTimer 延迟发射信号
QTimer::singleShot(1000, []() {
    qDebug() << "Delayed execution";
});

// 使用 QMetaObject::invokeMethod 延迟调用
QMetaObject::invokeMethod(receiver, "slotFunction", Qt::QueuedConnection);

性能优化 #

减少不必要的连接 #

cpp
// 使用 UniqueConnection 避免重复连接
connect(sender, &Sender::signal, receiver, &Receiver::slot,
        Qt::UniqueConnection);

// 及时断开不再需要的连接
disconnect(sender, &Sender::signal, receiver, &Receiver::slot);

避免信号循环 #

cpp
// 错误示例:信号循环
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);
connect(slider, &QSlider::valueChanged,
        spinBox, QOverload<int>::of(&QSpinBox::setValue));  // 循环!

// 解决方案:使用信号阻塞
spinBox->blockSignals(true);
slider->setValue(value);
spinBox->blockSignals(false);

下一步 #

现在你已经深入理解了信号与槽机制,接下来学习 事件系统,了解 Qt 的事件处理机制!

最后更新:2026-03-29