• Qt源码分析之QObject


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/a844651990/article/details/78801518
      在分析源码之前,我们先来介绍下Pimpl机制。。。

    Pimpl机制介绍
      Pimpl(private implementation) 字面意思是私有实现。具体实现是将类的(假设类A)私有数据和函数放入一个单独的类(假设类Pimpl)中,然后在类A的头文件中对该类Pimpl进行前置声明,接着在类A中声明一个私有的指向该Pimpl类的指针, 在类A的构造函数中分配类Pimpl,这样做的主要目的是解开类的使用接口和实现的耦合。

    为什么要使用 Pimpl机制
    1、更加符合c++的封装性
      封装有一个很重要的属性就是信息隐藏性,即利用接口机制隐蔽内部细节,达到将对象的使用者和设计者分开的目的,以提高软件的可维护性和可修改性。使用Pimpl机制,只要我们不修改公有接口,使用者永远看到的是同一个共有接口和一个指针。

    2、节约编译时间
      一个大型c++工程的编译所消耗的时间往往很感人。如果不使用Pimpl机制,我们修改了某一个头文件,那么将会导致所有包含该头文件的源文件都会被重新编译,那么编译成本就会很高。如果使用了Pimpl机制,就不会这样。

    例子:
    简单的打印m_msg的类

    printmsg.h

    #ifndef PRINTMSG_H
    #define PRINTMSG_H
    #include <QString>

    class PrintMsg
    {
    public:
    PrintMsg();
    void print();

    private:
    QString m_msg;
    };

    #endif // PRINTMSG_H
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      如果此时我们在printmsg.h 中添加一个m_msg1,所有包含printmsg.h的代码都会会被重新编译。

    #ifndef PRINTMSG_H
    #define PRINTMSG_H
    #include <QString>

    class PrintMsg
    {
    public:
    PrintMsg();

    void print();

    private:
    QString m_msg;
    QString m_msg1;
    };

    #endif // PRINTMSG_H
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    如果使用Pimpl机制:
    将具体实现放入PrintMsgPrivate类中:

    printmsgprivate.h

    #ifndef PRINTMSGPRIVATE_H
    #define PRINTMSGPRIVATE_H
    #include <QString>

    class PrintMsgPrivate
    {
    public:
    PrintMsgPrivate();

    void printmsg();

    private:
    QString m_msg;
    };

    #endif // PRINTMSGPRIVATE_H
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    PrintMsg类:

    #ifndef PRINTMSG_H
    #define PRINTMSG_H
    #include <QString>
    #include <QScopedPointer>

    class PrintMsgPrivate;
    class PrintMsg
    {
    public:
    PrintMsg();
    ~PrintMsg();

    void print();

    private:
    QScopedPointer<PrintMsgPrivate> m_pPrint;
    };

    #endif // PRINTMSG_H

    #include "printmsg.h"
    #include "pritmsgprivate.h"

    PrintMsg::PrintMsg()
    : m_pPrint(new PrintMsgPrivate())
    {

    }

    void PrintMsg::print()
    {
    m_pPrint->printmsg();
    }
    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
    29
    30
    31
    32
    33
    这里使用了Qt的智能指针类QScopedPointer,此时我们在PrintMsgPrivate中添加一个m_msg2, 再次编译只需要重新编译printmsgprivate.cpp 和 printmsg.cpp 即可。

    Qt源码分析之QObject
    原文: http://blog.csdn.net/oowgsoo/article/details/1529284

    用sizeof() 看一下QObject的大小为8,除了虚函数表占4个字节,另外四个字节是:

    class Q_CORE_EXPORT QObject
    {
    ...
    protected:
    QScopedPointer<QObjectData> d_ptr;
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    一个指向 QObjectData 的智能指针。这里就使用了Pimpl机制。来看一下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;
    };
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
      d_ptr与QObjectData中的q_ptr遥相呼应,使得接口类和实现类可以双向的引用。为什么是这样的命名方式呢?可能q指的是Qt接口类,d指的是Data数据类,这当然是猜测了,但是或许可以方便你记忆,在Qt中,这两个指针名字是非常重要的,必须记住,但是仅仅如此还是不容易使用这两个指针,因为它们都是基类的类型,难道每次使用都要类型转换吗?为了简单起见,Qt在这里声明了两个宏:

    #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;
    ...

    #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;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    qGetPtrHelper:

    template <typename T> static inline T *qGetPtrHelper(T *ptr)
    {
    return ptr;
    }
    1
    2
    3
    4
      只要在类的头文件中使用这两个宏,就可以通过函数直接得到实体类和接口类的实际类型了,而且这里还声明了友元,使得数据类和接口类连访问权限也不用顾忌了。我们再看一下Class##Private这个类,QObject中即QObjectPrivate:

    class Q_CORE_EXPORT QObjectPrivate : public QObjectData
    {
    Q_DECLARE_PUBLIC(QObject)
    ...
    };
    1
    2
    3
    4
    5
    通过查看源码发现,QObjectPrivate继承自QObjectData,而QObjectPrivate,这个类封装了线程处理,信号和槽机制等具体的实现,可以说它才是Qt实体类中真正起作用的基类,而QObjectData不过是一层浅浅的数据封装而已。

    为了cpp文件中调用的方便,更是直接声明了以下两个宏:

    #define Q_D(Class) Class##Private * const d = d_func()
    #define Q_Q(Class) Class * const q = q_func()
    1
    2
    QObject 中即:

    #define Q_D(QObject) QObjectPrivate * const d = d_func()
    #define Q_Q(QObject) QObject* const q = q_func()
    1
    2
    好了,使用起来倒是方便了,但是以后局部变量可千万不能声明为d和q了。
    这里的d_func和q_func函数是非常常用的函数,可以理解为一个是得到数据类,一个是得到Qt接口类。

    再来看QObjectData:

    QObject *parent;
    QObjectList children;
    1
    2
    再来看QObject中:

    class Q_CORE_EXPORT QObject
    {
    ...

    public:
    inline QObject *parent() const { return d_ptr->parent; }
    ...

    inline const QObjectList &children() const { return d_ptr->children; }
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      可以看出parent 指向了当前QObject的父类,children则保存了当前QObject的所有子类的指针。
      总之,Qt确实在内存中保存了所有类实例的树型结构。

    来看一个具体的实例,QPushButton,

    构造
    QPushButton的接口类派生关系是:

    QObject
    QWidget
    QAbstractButton
    QPushButton
    1
    2
    3
    4
    QPushButton的具体实现类派生关系是:

    QObjectData
    QObjectPrivate
    QWidgetPrivate
    QAbstractButtonPrivate
    QPushButtonPrivate
    1
    2
    3
    4
    5
    所有具体的实现都放在了Class##Private类中。
    通过QPushButton的一个构造函数来看看它们是如何联系的:

    QPushButton::QPushButton(QWidget *parent)
    : QAbstractButton(*new QPushButtonPrivate, parent)

    QAbstractButton 的一个构造函数:
    QAbstractButton(QAbstractButtonPrivate &dd, QWidget* parent)
    : QWidget(dd, parent, 0)

    QWidget的一个构造函数:
    QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)
    : QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice()

    QObject的一个构造函数:
    QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      首先QPushButton的构造函数中调用了QAbstractButton的构造函数,同时马上new出来一个QPushButtonPrivate实体类,然后把指针转换为引用传递给QAbstractButton,
    QAbstractButton的构造函数中继续调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类指针继续传给基类。

    QWidget继续坐着同样的事情:

    QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
    1
    2
    终于到了基类QObject,这里就直接把QPushButtonPrivate的指针赋值给了d_ptr。最终在QPushButton构造时同时产生的new QPushButtonPrivate被写到了QObject中的d_ptr中。

    QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
    {
    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;
    if (d->isWidget) {
    if (parent) {
    d->parent = parent;
    d->parent->d_func()->children.append(this);
    }
    // no events sent here, this is done at the end of the QWidget constructor
    } else {
    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);
    }
    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
    29
    30
    31
    然后执行QObject的构造函数,这里主要是一些线程的处理,先不理它

    QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
    : QObject(dd, 0), QPaintDevice()
    {
    Q_D(QWidget);
    QT_TRY {
    d->init(parent, f);
    } QT_CATCH(...) {
    QWidgetExceptionCleaner::cleanup(this, d_func());
    QT_RETHROW;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    然后是QWidget的构造函数,这里调用了数据类QWidgetPrivate的init函数,这个函数不是虚函数,因此静态解析成QWidgetPrivate的init函数调用。

    QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
    : QWidget(dd, parent, 0)
    {
    Q_D(QAbstractButton);
    d->init();
    }
    1
    2
    3
    4
    5
    6
    然后是QAbstractButton的构造函数,这里调用了数据类QAbstractButton的init函数,这个函数不是虚函数,因此静态解析成QAbstractButton的init函数调用。

    QPushButton::QPushButton(QWidget *parent)
    : QAbstractButton(*new QPushButtonPrivate, parent)
    {
    Q_D(QPushButton);
    d->init();
    }
    1
    2
    3
    4
    5
    6
    然后是QPushButton的构造函数,这里调用了数据类QPushButton的init函数,这个函数不是虚函数,因此静态解析成QPushButton的init函数调用。

    总结一下:
      QPushButton在构造的时候同时生成了QPushButtonPrivate针,QPushButtonPrivate创建时依次调用数据类基类的构造函数。QPushButton的构造函数中显示的调用了基类的构造函数并把QPushButtonPrivate指针传递过去,QPushButton创建时依次调用接口类基类的构造函数。在接口类的构造函数中调用了平行数据类的init函数,因为这个函数不是虚函数,因此就就是此次调用了数据类的init函数。

    析构
    然后是析构:

    QPushButton::~QPushButton()
    {
    }
    1
    2
    3
    QPuButton 析构函数。

    QAbstractButton::~QAbstractButton()
    {
    #ifndef QT_NO_BUTTONGROUP
    Q_D(QAbstractButton);
    if (d->group)
    d->group->removeButton(this);
    #endif
    }
    1
    2
    3
    4
    5
    6
    7
    8
    QAbstractButton 析构函数。

    QWidget::~QWidget()
    {
    Q_D(QWidget);
    ...
    }
    1
    2
    3
    4
    5
    QWidget析构函数,一大堆。

    QObject::~QObject()
    {
    Q_D(QObject);
    d->wasDeleted = true;
    ...

    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    QObject 析构函数。wasDeleted防止在多线程下被重复删除。

    if (!d->children.isEmpty())
    d->deleteChildren();
    1
    2
      这里清除所有子类指针,当然每个子类指针清除时又会清除它的所有子类,因此Qt中new出来的指针很少有显示对应的delete,因为只要最上面的指针被框架删除了,它所连带的所有子类都被自动删除了。QObject 的析构函数还做了大量的其它的删除清理工作,大家自行研究。。。
    ————————————————
    版权声明:本文为CSDN博主「FlyWM_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/a844651990/article/details/78801518

  • 相关阅读:
    window系统中,解决Pycharm 文件更改目录后,执行路径未更新问题(转)
    yum安装软件报错–skip-broken(卸载的时候一直报错:未安装软件包)
    selenium之Chrome浏览器与chromedriver对应关系和下载
    Python 第三方库 批量下载安装包,离线批量安装Python第三方库
    UIpath 循环读取IMAP邮件,并保存附件
    UIpath 中如何使用 正则表达式
    用python实现队列,堆栈
    python-反射
    Python基础-父类对子类的约束
    Python基础-类的继承顺序
  • 原文地址:https://www.cnblogs.com/findumars/p/11253344.html
Copyright © 2020-2023  润新知