QT信号与槽机制

connect 连接

QObject::connect()是实现信号与槽机制的核心函数,用于建立对象间的通信。

1. 基本语法

  • Qt5 + 推荐语法(类型安全)
connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName, connectionType);
  1. sender : 发送者
  2. SenderClass::signalName : 发送信号名称
  3. receiver : 接收者
  4. ReceiverClass::slotName :接收后调用的槽函数名称
  5. connectionType :连接类型

2. 连接类型

  • Qt::AutoConnection (默认)
    自动判断:同线程使用DirectConnection,跨线程使用QueuedConnection
  • Qt::DirectConnection
    槽函数在信号发送线程立即执行(类似函数调用)。
  • Qt::QueuedConnection
    槽函数在接收者线程的事件循环中异步执行(跨线程安全)。
  • Qt::BlockingQueuedConnection
    类似QueuedConnection,但阻塞发送者线程直到槽执行完毕(慎用,需防死锁)。
  • Qt::UniqueConnection
    避免重复连接(与上述类型按位或组合使用

3. 信号与槽要求

  • 信号和槽声明
    在类中使用 signals/slots 关键字声明,或使用 Q_SIGNALS/Q_SLOTS
  • 元对象系统
    类必须继承 QObject 并在类定义开头添加 Q_OBJECT 宏(用于元对象编译 MOC)

4. 连接方式扩展

  • 连接重载信号

使用静态转型或 qOverload<>(C++14):

由于信号可以有多个重载(例如,QComboBox::currentIndexChanged 有两个重载:一个带有 int 参数,另一个带有 const QString & 参数 ),因此需要明确指定要连接的是哪一个版本。

void QComboBox::currentIndexChanged(int index);         // 版本1:带整型参数
void QComboBox::currentIndexChanged(const QString& text); // 版本2:带字符串参数

当使用 Qt5 的新型语法(基于函数指针)进行信号槽连接时:

// 处理重载信号
connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);

编译器无法自动确定要使用哪个重载版本,因此需要用 QOverload 明确指出目标重载。

QOverload<int>::of 详解

1. 语法结构

verload<参数类型>::of(成员函数指针)

2. 在连接中的使用

// 明确选择 currentIndexChanged(int) 重载版本
QOverload<int>::of(&QComboBox::currentIndexChanged)

3. 从 Qt5 到 Qt6 的变化

版本 解决方案 示例
Qt5 (5.0-5.6) 显式类型转换 static_cast<void (QComboBox:😗)(int)>(&QComboBox::currentIndexChanged)
Qt5 (>=5.7) QOverload::of QOverload::of(&QComboBox::currentIndexChanged)
Qt6 简化的 qOverload qOverload(&QComboBox::currentIndexChanged)
  • Lambda 表达式作为槽
connect(button, &QPushButton::clicked, [=]() {
    qDebug() << "Button clicked!";
});

注意:Lambda 中捕获局部变量时,需确保生命周期(跨线程时谨慎捕获)。

  • 信号连接信号
connect(srcObj, &SrcClass::signalA, destObj, &DestClass::signalB);

5. 断开连接

  • 手动断开
disconnect(sender, nullptr, receiver, nullptr);  // 断开 sender 与 receiver 所有连接
disconnect(connectionHandle);                   // 通过 QMetaObject::Connection 断开
  • 自动断开
    senderreceiver 被销毁时,连接自动断开。

6. 连接有效性检查

  • 返回值
    connect() 返回 QMetaObject::Connection 对象,用于管理连接状态。
  • 检查连接
QMetaObject::Connection conn = connect(...);
if (conn) { /* 连接成功 */ }

7. 多线程注意事项

  • 跨线程通信
    必须使用 QueuedConnectionBlockingQueuedConnection
  • 线程亲和性
    通过 QObject::thread()moveToThread() 管理对象所属线程。
  • 资源竞争
    QueuedConnection 传递的参数必须是元类型(使用 qRegisterMetaType() 注册复杂类型)。

8. Qt 传递自定义类型

在QT中使用槽与信号传递自定义类型,需要先进行类型注册。

方式1:使用 qRegisterMetaType<T>() 注册自定义类型

  • 作用:在运行时将类型的元信息注册到Qt的类型系统中。使得Type可用于跨线程的信号槽通信。
  • 阶段:运行时 (Run-time),通常在 main 函数或初始化代码中调用。
  • QVariant: 非必需,但建议一起使用。QVariant 的某些操作(如跨线程转换)可能需要它
  • 信号槽(自动连接):必需。用于排队连接 (QueuedConnection),这是跨线程通信的默认方式。

信号槽(自动连接):不足以支持跨线程,同线程的直连(DirectConnection)可以,因为它不涉及拷贝。

方式2:在类声明中使用 Q_DECLARE_METATYPE(Type) 声明自定义类型

  • 作用:让编译器在编译时生成类型的元信息,使得Type可用于QVariant,QSettings等Qt的跨线程通信。
  • 阶段:编译时(Compiler Time)
  • QVariant: 必需。没有它,QVariant无法识别或存储自定义类型。
  • 信号槽(自动连接):不足以支持跨线程,同线程的直连(DirectConnection)可以,因为它不涉及拷贝。

总结

  • 两者关系:它们是互补的,而不是二选一。对于需要跨线程传递的自定义类型,两者通常都需要。
  • 正确流程:
    • 在头文件中声明:Q_DECLARE_METATYPE(MyCustomType)
    • 在源文件中注册(通常在 main 函数或某个初始化函数中):qRegisterMetaType("MyCustomType");
    • 如果类型在命名空间中:需要在外层声明和注册,例如 Q_DECLARE_METATYPE(MyNamespace::MyCustomType)
#include <QMetaType> // 必须包含这个头文件
// 自定义数据类型
class MyDataType
{
    //省略成员变量和方法...
};

//在头文件中声明
Q_DECLARE_METATYPE(MyCustomType);
// main 函数或某个初始化函数中
// 关键步骤:在运行时注册自定义类型
// 这是跨线程信号槽传递所必需的
qRegisterMetaType<MyDataType>("MyDataType");
qRegisterMetaType<QSharedPointer<MyDataType>>("QSharedPointer<MyDataType>");
// 如果需要,也可以注册自定义类型的指针版本
// qRegisterMetaType<MyDataType*>("MyDataType*");

配合智能指针使用

对于Qt跨线程通信,推荐使用一下智能指针:

  • QSharedPointer<T>:用于跨线程传递复杂数据,其内部维护一个引用计数,当引用计数为0时自动释放内存。
  • QWeakPointer<T>:用于跨线程传递复杂数据,其内部维护一个弱引用,当引用对象被销毁时自动失效(配合QSharedPointer使用)
  • std::shared_ptr : C++11 标准智能指针,也需要注册才能使用
#include <QString>
#include <QMetaType>
#include <QSharedPointer>
#include <memory>

// 前置声明
class SmartData;

// 定义智能指针类型别名  一般使用智能指针都是要起别名的,方便使用
using SmartDataPtr = QSharedPointer<SmartData>; 
using SmartDataStdPtr = std::shared_ptr<SmartData>;

// 自定义数据类型
class SmartData
{
public:
    SmartData();
    SmartData(int id, const QString& name, double value);
    
    // 不需要显式定义拷贝构造函数和赋值运算符
    // 因为使用智能指针,不会直接拷贝对象
    
    QString toString() const;
    
    int id() const;
    QString name() const;
    double value() const;
    void setValue(double value);

private:
    int m_id;
    QString m_name;
    double m_value;
};

// 声明元类型 - 编译时声明
// 1. 声明原始类型(可选,如果需要在QVariant中使用)
Q_DECLARE_METATYPE(SmartData)

// 2. 声明智能指针类型(必须)
Q_DECLARE_METATYPE(SmartDataPtr)
Q_DECLARE_METATYPE(SmartDataStdPtr)
// 在主函数中
// 关键步骤:在运行时注册自定义类型和智能指针类型
// 1. 注册原始类型(如果需要)
qRegisterMetaType<SmartData>("SmartData");

// 2. 注册智能指针类型(必须)
qRegisterMetaType<SmartDataPtr>("SmartDataPtr");
qRegisterMetaType<SmartDataStdPtr>("SmartDataStdPtr");

// 3. 也可以使用模板方式注册(推荐)
qRegisterMetaType<QSharedPointer<SmartData>>();
qRegisterMetaType<std::shared_ptr<SmartData>>();

SmartController controller;
controller.startDemo();
posted @ 2025-09-02 18:03  一楼二栋  阅读(49)  评论(0)    收藏  举报