zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Qt源码分析之QObject

Qt源码 分析
2023-09-11 14:19:07 时间

版权声明:本文为博主原创文章,遵循 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