QObject学习

本文详细介绍了Qt的QObject类,包括其大小、成员变量、构造函数、d_ptr和p_ptr机制,以及QObjectData的数据结构。还探讨了构造函数中的线程管理、信号槽连接、阻塞信号、启动和停止定时器的方法。同时,强调了QObject禁止赋值和拷贝构造的原因。最后,分析了QObject的connect函数,包括参数检查、线程安全和信号槽匹配规则。
摘要由CSDN通过智能技术生成

 

做一下QObject的相关笔记,准备来一个系统性的学习。

以下内容全部是个人理解,有谬误的地方欢迎大佬指出!

QObject的成员变量

首先,我们先看一下QObject的大小:

    qDebug()<<sizeof(QObject);

结果输出为8,我们打开QObject的源码,可以发现这8个字节是类的虚表指针和一个指针成员变量

QScopedPointer<QObjectData> d_ptr;

点开定义发现QScopedPointer是一个模板类,内部只有一个T*的指针,所以这个d_ptr其实就是一个QObjectData的指针,定义如下:

class Q_CORE_EXPORT QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;
    QObject *parent;
    QObjectList children;

    uint isWidget : 1;
    uint blockSig : 1;
    uint wasDeleted : 1;
    uint isDeletingChildren : 1;
    uint sendChildEvents : 1;
    uint receiveChildEvents : 1;
    uint isWindow : 1; //for QWindow
    uint unused : 25;
    int postedEvents;
    QDynamicMetaObjectData *metaObject;
    QMetaObject *dynamicMetaObject() const;
};

这里面储存了QObject的一些信息,包括父节点指针,子节点指针,是否为Widget等。

这样,QObject中包含了所有的方法,QObjectDate里存储数据信息,这就是QT的句柄实例设计模式,QObject是句柄类,QObjectDate则是实例类。所以,QObject的派生类可以说有两个基类,一个是QObject,另一个是QObjectDate。由此展开了d_ptr和p_ptr,这个以后在复习d指针的时候再展开。

这样设计有什么好处,具体可以参考Pimpl机制,可以隐藏信息,降低耦合。

接下来,我们看构造函数:

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    if (parent) {
        QT_TRY {
            if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
                parent = 0;
            setParent(parent);
        } QT_CATCH(...) {
            d->threadData->deref();
            QT_RETHROW;
        }
    }
#if QT_VERSION < 0x60000
    qt_addObject(this);
#endif
    if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
        reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
}

我们注意到,调用构造函数的时候,我们new了一个QObjectPrivate给d_ptr赋值,翻看源码可以发现,QObjectPrivate继承了QObjectDate,并且提供了一些线程以及信号槽相关的具体实现,其实,所有的QObject的派生类都会在构造函数中new一个相应的private类,比如QPushButton:

QPushButton::QPushButton(QWidget *parent)
    : QAbstractButton(*new QPushButtonPrivate, parent)
{
    Q_D(QPushButton);
    d->init();
}

这个QpushButtonPrivate的继承关系里就有一环继承了QObjectPrivate。这里面的Q_D展开来就是QPushButtonPrivate* const d = d_func。

至于QObjectPrivate如何进行信号槽的实现,留到以后再记录。

对了,值得一提的是,QObject禁用了赋值和拷贝构造函数,因为QObject有唯一的名字,而且处于树型结构中,如果可以复制,无疑会增添很多麻烦,更不用说如何处理这些已经连接上的信号槽。

继续说回QObject的构造函数。

Q_D(QObject)的作用可以参考下面的宏的解释,其中变量d的类型为QObjectPrivate*,我们初始化了线程threadDate。

QObject的宏定义

1,Q_OBJECT

    以后分析元对象系统的时候再梳理

2,Q_PROPERTY

    QT属性,留待以后分析

3,Q_DECLARE_PRIVATE

#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
    friend class Class##Private;

在这个宏中,创建了两个内联函数,将d_ptr转换成ClassPrivate*返回,帮我们获取指向实例的指针,而且还声明了友元类,可以让我们省去了访问权限的问题。值得一提的是,同名同参的函数如果返回值的const属性不同,也可以实现重载。

4,Q_DECLARE_PUBLIC

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;

同上面的宏类似,帮我们获取指向句柄的指针,在QObjectPrivate中使用。

5,Q_D

#define Q_D(Class) Class##Private * const d = d_func()

定义了一个变量d指向了实例指针

6,Q_Q

#define Q_Q(Class) Class * const q = q_func()

定义了变量q指向了句柄指针

 

QObject的接口

1, QThread *thread() const;

     返回对象所在的线程。

 

2,void moveToThread(QThread *thread);

    if (d->threadData->thread == targetThread) {
        // object is already in this thread
        return;
    }

这个很好理解,如果自身线程和目标线程相同,就什么都不做

    if (d->parent != 0) {
        qWarning("QObject::moveToThread: Cannot move objects with a parent");
        return;
    }

被move的对象不能有父对象,不然无法move成功。

    if (d->isWidget) {
        qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
        return;
    }
    if (d->isWidget) {
        qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread");
        return;
    }

QWidget只能在主线程。

    QThreadData *currentData = QThreadData::current();
    QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : Q_NULLPTR;
    if (d->threadData->thread == 0 && currentData == targetData) {
        // one exception to the rule: we allow moving objects with no thread affinity to the current thread
        currentData = d->threadData;
    } else if (d->threadData != currentData) {
        qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p).\n"
                 "Cannot move to target thread (%p)\n",
                 currentData->thread.load(), d->threadData->thread.load(), targetData ? targetData->thread.load() : Q_NULLPTR);

        return;
    }

如果当前线程不是对象所在的线程,那么我们也无法将对象移动到目标线程,除非对象与当前线程没有关联。

   // prepare to move
    d->moveToThread_helper();

    if (!targetData)
        targetData = new QThreadData(0);

    QOrderedMutexLocker locker(&currentData->postEventList.mutex,
                               &targetData->postEventList.mutex);

    // keep currentData alive (since we've got it locked)
    currentData->ref();

    // move the object
    d_func()->setThreadData_helper(currentData, targetData);

    locker.unlock();

    // now currentData can commit suicide if it wants to
    currentData->deref();

然后我们将对象移动到了目标线程。

 

3, static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,Qt::ConnectionType type = Qt::AutoConnection);

    if (sender == 0
            || receiver == 0
            || signal.methodType() != QMetaMethod::Signal
            || method.methodType() == QMetaMethod::Constructor) {
        qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(null)",
                 signal.methodSignature().constData(),
                 receiver ? receiver->metaObject()->className() : "(null)",
                 method.methodSignature().constData() );
        return QMetaObject::Connection(0);
    }

信号的发送者和接收者都不能为空,而且第二个参数signal的类型必须为Signal,第四个参数method不能为构造函数,不然直接返回。

    int signal_index;
    int method_index;
    {
        int dummy;
        QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
        QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
    }

    const QMetaObject *smeta = sender->metaObject();
    const QMetaObject *rmeta = receiver->metaObject();
    if (signal_index == -1) {
        qWarning("QObject::connect: Can't find signal %s on instance of class %s",
                 signal.methodSignature().constData(), smeta->className());
        return QMetaObject::Connection(0);
    }
    if (method_index == -1) {
        qWarning("QObject::connect: Can't find method %s on instance of class %s",
                 method.methodSignature().constData(), rmeta->className());
        return QMetaObject::Connection(0);
    }

    if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 smeta->className(), signal.methodSignature().constData(),
                 rmeta->className(), method.methodSignature().constData());
        return QMetaObject::Connection(0);
    }

检查当作参数的信号和槽函数是否真实存在,如果不存在也返回空。

    if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 smeta->className(), signal.methodSignature().constData(),
                 rmeta->className(), method.methodSignature().constData());
        return QMetaObject::Connection(0);
    }

检查信号的形参是否一致。

    int *types = 0;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(signal.parameterTypes())))
        return QMetaObject::Connection(0);

如果采用多线程的枚举来连接,要检查参数是否为元数据,如果是自定义的数据,那么必须调用qRegisterMetaType进行注册,不然会连接失败。另外,如果参数是引用类型,也无法跨线程连接。

#ifndef QT_NO_DEBUG
    check_and_warn_compat(smeta, signal, rmeta, method);
#endif
    QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
    return handle;

连接成功。另外,对于重载的情况,我们可以使用QOverload<T>::of来进行转化,达到信号和曹的匹配。

 

4,bool blockSignals(bool b) Q_DECL_NOTHROW;

源码很简单,没啥好说的,作用是,当b为真的时候,阻塞信号的发出,为假的时候,取消阻塞。

 

5,int startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer);

这个接口用来启动一个定时器事件,并返回定时器id,如果失败就返回0。定时器每隔interval 毫秒就会启动一次,直到调用killTimer()。当定时器发生时,会调用timerEvent(QTimerEvent *event).如果多个定时器在运行,可用通过定时器id来区分。

    if (Q_UNLIKELY(interval < 0)) {
        qWarning("QObject::startTimer: Timers cannot have negative intervals");
        return 0;
    }

时间不能为负数。

    if (Q_UNLIKELY(!d->threadData->eventDispatcher.load())) {
        qWarning("QObject::startTimer: Timers can only be used with threads started with QThread");
        return 0;
    }

这也是经常能碰到的情况。可以通过连接 QThread :: started信号 触发的槽函数启动定时器来解决。

 

    if (Q_UNLIKELY(thread() != QThread::currentThread())) {
        qWarning("QObject::startTimer: Timers cannot be started from another thread");
        return 0;
    }

定时器所处的线程要和当前调用的线程相同才行

    int timerId = d->threadData->eventDispatcher.load()->registerTimer(interval, timerType, this);
    if (!d->extraData)
        d->extraData = new QObjectPrivate::ExtraData;
    d->extraData->runningTimers.append(timerId);
    return timerId;

接下来就启动成功,返回相应的定时器id。

 

6, void killTimer(int id);

  用法就是停止对应的定时器。

    Q_D(QObject);
    if (Q_UNLIKELY(thread() != QThread::currentThread())) {
        qWarning("QObject::killTimer: Timers cannot be stopped from another thread");
        return;
    }

也是经常能遇到的问题,我们定时器对象所处的线程必须和当前调用函数的线程相同。

    if (id) {
        int at = d->extraData ? d->extraData->runningTimers.indexOf(id) : -1;
        if (at == -1) {
            // timer isn't owned by this object
            qWarning("QObject::killTimer(): Error: timer id %d is not valid for object %p (%s, %s), timer has not been killed",
                     id,
                     this,
                     metaObject()->className(),
                     qPrintable(objectName()));
            return;
        }

这个定时器id必须由这个对象产生的才能调用。

        if (d->threadData->eventDispatcher.load())
            d->threadData->eventDispatcher.load()->unregisterTimer(id);

        d->extraData->runningTimers.remove(at);
        QAbstractEventDispatcherPrivate::releaseTimerId(id);

开始定时器就是d->extraData->runningTimers加入时间id,结束就是remove掉。

 

 

 

 

                                                  (这个图太久了,忘记在哪里看到了,直接用了,如果侵犯了您的权益请私信本人删除)

好的,我可以给你一个简单的例子来演示如何创建和使用QObject类。 首先,在Qt Creator中创建一个新的Qt Widgets应用程序项目。然后在项目中创建一个名为"MyObject"的新类,这个类继承自QObject。 MyObject.h文件代码如下: ``` #ifndef MYOBJECT_H #define MYOBJECT_H #include <QObject> class MyObject : public QObject { Q_OBJECT public: explicit MyObject(QObject *parent = nullptr); public slots: void doSomething(); }; #endif // MYOBJECT_H ``` MyObject.cpp文件代码如下: ``` #include "myobject.h" #include <QDebug> MyObject::MyObject(QObject *parent) : QObject(parent) { } void MyObject::doSomething() { qDebug() << "MyObject is doing something..."; } ``` 在主窗口中,创建MyObject类的实例,并调用其槽函数doSomething(),代码如下: ``` #include "mainwindow.h" #include "ui_mainwindow.h" #include "myobject.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 创建MyObject类的实例 MyObject *myObject = new MyObject(this); // 调用MyObject类的槽函数doSomething() myObject->doSomething(); } MainWindow::~MainWindow() { delete ui; } ``` 现在你可以编译并运行这个程序,你将会看到" MyObject is doing something... "的输出。这说明我们已经成功地创建了一个继承自QObject的类,并且在主窗口中使用它。 这只是一个简单的例子,QObject类还有很多其他强大的功能,比如信号和槽机制等,你可以在实际开发中深入学习和使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值