Qt Creator:避免QRunnable和QObject多重继承

目录

1.前言

2.简单介绍QRunnable

3.如何使用QRunnable

4.为什么要继承QObject

5.如何避免多重继承QRunnable和QObject

6.QMetaObject::invokeMethod进阶版


前言

Qt中主要存在三种使用线程的方式,分别是QThread,QRunnable和moveToThread。其中QThread的使用场景主要是简单的独立任务,或不需频繁交互的耗时操作,QRunnable的使用场景主要是复杂的、需长时间运行并与主线程频繁交互的后台服务,而moveToThread的使用场景则是大量独立、无状态的短期任务。本篇文章主要是讲解QRunnable,而由于大多数博客在使用的时候都是采用多重继承的方式继承了QRunnable和QObject,这种方式并没有保持QRunnable轻量的特性。故此本篇文章将解决这个问题,使用QMetaObject单独继承QRunnable。


简单介绍QRunnable

在讲解解决方案之前,先了解一下QRunnable。

1.QRunnable的核心优势是极致的轻量,内存与性能开销小,代码复杂度低

2.QRunnable的缺点是不属于QObject的子类,导致不能使用信号槽

3.QRunnable的使用场景是大量、独立、生命周期短的​​计算密集型任务


如何使用QRunnable

对于如何使用QRunnable,以下是示例代码(不保证能运行):

cpp 复制代码
//----------第一步,自定义类型继承自QRunnable-----------
#include <QRunnable>
class Task : public QRunnable
{
public:
    explicit Task(){
        setAutoDelete(true);    // 自动删除对象
    }
    void run(){
        qDebug() << "WildPointer"; 
    }
};

//----------第而步,创建对象使用线程池启动线程-----------
int main(int argc, char *argv[])
{
    Task *test = new Task();
    QThreadPool::globalInstance()->start(task);    // 使用全局线程池激活线程
}

为什么要继承QObject

大多数Qt开发者习惯使用信号槽的方式去通知其他对象事件处理的结束,当然我们也可以使用全局对象,共享内存,锁等方式实现,但是在Qt下能使用信号槽实现相同的功能,代码简单容易实现何乐而不为呢?但是由于QRunnable并不是QObject的子类,所以单独继承QRunnable不能使用信号槽,于是大多数情况便采用多重继承的方式同时继承QRunnable和QObject。这种实现方式并不能保持QRunnable轻量级减少开销,而且还必须将 QObject写在继承列表的第一个位置才能正确正确的生成元对象代码,否则会在编译时报错。

cpp 复制代码
#include <QObject>
#include <QRunnable>

// 同时继承QRunnable和QObject
class Task : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit Task(QObject *parent = nullptr);
    void run() override;
};

如何避免多重继承QRunnable和QObject

为了避免同时继承QRunnable和QObjet,将采用QMetaObject::invokeMethod。该函数的原理如图所示:

图1.QMetaObject::invokeMethod调用原理

在使用QMetaObject::invokeMethod调用函数之前,需要将被调用的函数使用Q_INVOKABLE宏来声明。Q_INVOKABLE关键字是一个宏,用于标记一个普通的C++成员函数,使其能够被Qt的元对象系统识别,从而能被QMetaObject::invokeMethod进行动态调用。具体的使用Q_INVOKABLE的代码如下:

cpp 复制代码
// 使用Q_INVOKABLE声明函数
Q_INVOKABLE void Function(QString str);

// 函数实现
Q_INVOKABLE void Function(QString str){
    qDebug() << str;
}

在我们使用Q_INVOKABLE声明被调用的函数以后,我们需要在继承自QRunnable类型中使用QMetaObject::invokeMethod调用。具体步骤如下:
1.继承QRunnable类型并且在构造函数中添加父类作为形参

cpp 复制代码
#include <QObject>
#include <QRunnable>

class WildPointer : public QRunnable
{
private:
    QObject m_Obj = nullptr;	// 调用对象
public:
	~ WildPointer();
	void run() override;
    // 大多数Qt中的类型都继承自QObject,所以此处使用QObject作示例
	explicit WildPointer(QObject *parent);
};

// 构造函数的实现
WildPointer::WildPointer(QObject* parent)
    , m_Obj(parent)
{
    setAutoDelete(true);
}

2.在需要调用外部函数的时候,使用QMetaObject::invokeMethod

cpp 复制代码
void WildPointer::Function() {
    // 调用被 Q_INVOKABLE 标记的函数
    QMetaObject::invokeMethod(m_Obj, "Function", Qt::AutoConnection, Q_ARG(QString, QString("WildPointer")));
}

QMetaObject::invokeMethod进阶版

当我们调用继承自QRunnable类型的对象变多的时候,类型可能也会不同。此时我们需要针对不同的类型调用对应类型中使用Q_INVOKABLE宏声明的函数,这个时候我们单独写一条QMetaObject::invokeMethod函数调用会导致很难对很多类型都做适配,于是我们可以对传入的类对象进行判断,执行不同类中的函数,具体修改如下:
1.在继承自QRunnable类的类型中定义回调函数指针

cpp 复制代码
#include <QObject>
#include <QRunnable>
#include <functional>

class WildPointer : public QRunnable
{
private:
    QObject m_Obj = nullptr;	// 调用对象
    std::function<void(bool, QString)> m_OnResult; // 回调函数
public:
	~ WildPointer();
	void run() override;
    // 大多数Qt中的类型都继承自QObject,所以此处使用QObject作示例
	explicit WildPointer(QObject *parent);
};

// 构造函数的实现
WildPointer::WildPointer(QObject* parent)
    , m_Obj(parent)
{
    setAutoDelete(true);
}

2.定义设置回调函数的接口

cpp 复制代码
// 设置结果回调
void WildPointer::setOnResultCallback(std::function<void(bool, QString)> callback) {
    m_OnResult = std::move(callback);
}

3.通过QMetaObject::invokeMethod调用回调函数

cpp 复制代码
void WildPointer::ReturnResultMessage(bool state, QString message) {
    if (m_fpObj) {
        // 将回调切回创建者线程执行,确保线程安全
        QMetaObject::invokeMethod(m_Obj, [callback = m_OnResult, result = state, msg = std::move(message)](){
            if (callback) {
                callback(result, msg);
            }
        }, Qt::AutoConnection);
    }
}

PS:通过进阶版的示例,我们可以实现一个供任意对象调用的模块,并且动态的调用每一个对象中的函数,实现所谓的反射

相关推荐
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner5 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz10 天前
QML Hello World 入门示例
qt
xcyxiner13 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner14 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript