Qt源码分析之QObject

2023-11-05

  在分析源码之前,我们先来介绍下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

  如果此时我们在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

如果使用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

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();
}

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓ 

这里使用了Qt的智能指针类QScopedPointer,此时我们在PrintMsgPrivate中添加一个m_msg2, 再次编译只需要重新编译printmsgprivate.cpp 和 printmsg.cpp 即可。

Qt源码分析之QObject

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

class Q_CORE_EXPORT QObject
{
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
}

一个指向 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;
};
}

  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;
qGetPtrHelper:
template <typename T> static inline T *qGetPtrHelper(T *ptr)
{
return ptr;
}

  只要在类的头文件中使用这两个宏,就可以通过函数直接得到实体类和接口类的实际类型了,而且这里还声明了友元,使得数据类和接口类连访问权限也不用顾忌了。我们再看一下Class##Private这个类,QObject中即QObjectPrivate:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
...
};

通过查看源码发现,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()

QObject 中即:

#define Q_D(QObject) QObjectPrivate * const d = d_func()
#define Q_Q(QObject) QObject* const q = q_func()

好了,使用起来倒是方便了,但是以后局部变量可千万不能声明为d和q了。

这里的d_func和q_func函数是非常常用的函数,可以理解为一个是得到数据类,一个是得到Qt接口类。

再来看QObjectData:

QObject *parent;
QObjectList children;

再来看QObject中:

class Q_CORE_EXPORT QObject
{
...
public:
inline QObject *parent() const { return d_ptr->parent; }
...
inline const QObjectList &children() const { return d_ptr->children; }
};

  可以看出parent 指向了当前QObject的父类,children则保存了当前QObject的所有子类的指针。

  总之,Qt确实在内存中保存了所有类实例的树型结构。

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

构造

QPushButton的接口类派生关系是:

QObject
QWidget
QAbstractButton
QPushButton

QPushButton的具体实现类派生关系是:

QObjectData
QObjectPrivate
QWidgetPrivate
QAbstractButtonPrivate
QPushButtonPrivate

所有具体的实现都放在了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)

  首先QPushButton的构造函数中调用了QAbstractButton的构造函数,同时马上new出来一个QPushButtonPrivate实体类,然后把指针转换为引用传递给QAbstractButton,

QAbstractButton的构造函数中继续调用基类QWidget的构造函数,同时把QPushButtonPrivate实体类指针继续传给基类。

QWidget继续坐着同样的事情:

QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)

终于到了基类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);
}

然后执行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;
}
}

然后是QWidget的构造函数,这里调用了数据类QWidgetPrivate的init函数,这个函数不是虚函数,因此静态解析成QWidgetPrivate的init函数调用。

QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
: QWidget(dd, parent, 0)
{
Q_D(QAbstractButton);
d->init();
}

然后是QAbstractButton的构造函数,这里调用了数据类QAbstractButton的init函数,这个函数不是虚函数,因此静态解析成QAbstractButton的init函数调用。

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

然后是QPushButton的构造函数,这里调用了数据类QPushButton的init函数,这个函数不是虚函数,因此静态解析成QPushButton的init函数调用。

总结一下:

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

析构

然后是析构:

QPushButton::~QPushButton()
{
}

QPuButton 析构函数。

QAbstractButton::~QAbstractButton()
{
#ifndef QT_NO_BUTTONGROUP
Q_D(QAbstractButton);
if (d->group)
d->group->removeButton(this);
#endif
}

QAbstractButton 析构函数。

QWidget::~QWidget()
{
Q_D(QWidget);
...
}

QWidget析构函数,一大堆。

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

QObject 析构函数。wasDeleted防止在多线程下被重复删除。

if (!d->children.isEmpty())
d->deleteChildren();

  这里清除所有子类指针,当然每个子类指针清除时又会清除它的所有子类,因此Qt中new出来的指针很少有显示对应的delete,因为只要最上面的指针被框架删除了,它所连带的所有子类都被自动删除了。QObject 的析构函数还做了大量的其它的删除清理工作,大家自行研究。。。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt源码分析之QObject 的相关文章

  • VLC 媒体播放器有 C# 界面吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 是否可以使用 C 控制台应用程序中的包装器从 VLC 播放中当前播放的文件中读取曲目统计信息 时间 标
  • 使用链表进行堆排序

    我想知道是否有人曾经使用链表进行堆排序 如果他们可以提供代码 我已经能够使用数组进行堆排序 但尝试在链表中进行排序似乎不切实际 而且在你知道的地方很痛苦 我必须为我正在做的项目实现链接列表 任何帮助将不胜感激 我也用C 答案是 你不想在链表
  • C# 静态类型不能用作参数

    public static void SendEmail String from String To String Subject String HTML String AttachmentPath null String Attachme
  • 静态构造函数和 BeforeFieldInit?

    如果类型没有静态构造函数 则将执行字段初始值设定项 就在使用该类型之前 或者在某个时间点突发奇想 运行时 为什么这段代码 void Main start Dump Test EchoAndReturn Hello end Dump clas
  • 使用 ADAL v3 使用 ClientID 对 Dynamics 365 进行身份验证

    我正在尝试对我们的在线 Dynamics CRM 进行身份验证以使用可用的 API 我能找到的唯一关于执行此操作的官方文档是 https learn microsoft com en us dynamics365 customer enga
  • 为什么 C 程序使用 Scanf 给出奇怪的输出?

    我目前正在学习 C 编程 并且遇到了这个奇怪的输出 Program will try functionalities of the scanf function include
  • 在 C++ 代码中转换字符串

    我正在学习 C 并开发一个项目来练习 但现在我想在代码中转换一个变量 字符串 就像这样 用户有一个包含 C 代码的文件 但我希望我的程序读取该文件并插入将其写入代码中 如下所示 include
  • 在 Mono 中反序列化 JSON 数据

    使用 Monodroid 时 是否有一种简单的方法可以将简单的 JSON 字符串反序列化为 NET 对象 System Json 只提供序列化 不提供反序列化 我尝试过的各种第三方库都会导致 Mono Monodroid 出现问题 谢谢 f
  • 防止控制台应用程序中的内存工作集最小化?

    我想防止控制台应用程序中的内存工作集最小化 在Windows应用程序中 我可以这样做覆盖 SC MINIMIZE 消息 http support microsoft com kb 293215 en us fr 1 但是 如何在控制台应用程
  • 如何向 Mono.ZeroConf 注册服务?

    我正在尝试测试 ZeroConf 示例http www mono project com Mono Zeroconf http www mono project com Mono Zeroconf 我正在运行 OpenSuse 11 和 M
  • JavaScript 错误:MVC2 视图中的条件编译已关闭

    我试图在 MVC2 视图页面中单击时调用 JavaScript 函数 a href Select a JavaScript 函数 function SelectBenefit id code alert id alert code 这里 b
  • OpenGL:如何检查用户是否支持glGenBuffers()?

    我检查了文档 它说 OpenGL 版本必须至少为 1 5 才能制作glGenBuffers 工作 用户使用的是1 5版本但是函数调用会导致崩溃 这是文档中的错误 还是用户的驱动程序问题 我正在用这个glGenBuffers 对于VBO 我如
  • LinkLabel 无下划线 - Compact Framework

    我正在使用 Microsoft Compact Framework 开发 Windows CE 应用程序 我必须使用 LinkLabel 它必须是白色且没有下划线 因此 在设计器中 我将字体颜色修改为白色 并在字体对话框中取消选中 下划线
  • 我们可以通过指针来改变const定义的对象的值吗?

    include
  • 以编程方式创建 Blob 存储容器

    我有一个要求 即在创建公司时 在我的 storageaccount 中创建关联的 blob 存储容器 并将容器名称设置为传入的字符串变量 我已尝试以下操作 public void AddCompanyStorage string subDo
  • Xamarin Forms Binding - 访问父属性

    我无法访问页面的 ViewModel 属性以便将其绑定到 IsVisible 属性 如果我不设置 BindingContext 我只能绑定它 有没有办法可以在设置 BindingContext 的同时访问页面的 viewmodel root
  • 在哪里可以找到 Microsoft.Build.Utilities.v3.5

    如何获取 Microsoft Build Utilities v3 5 我正在使用 StyleCop 4 7 Stylecop dll 中的 StyleCop msbuild 任务似乎依赖于 Microsoft Build Utilitie
  • C:设置变量范围内所有位的最有效方法

    让我们来int举个例子 int SetBitWithinRange const unsigned from const unsigned to To be implemented SetBitWithinRange应该返回一个int其中所有
  • 如何组合两个 lambda [重复]

    这个问题在这里已经有答案了 可能的重复 在 C 中组合两个 lambda 表达式 https stackoverflow com questions 1717444 combining two lamba expressions in c
  • ContentDialog Windows 10 Mobile XAML - 全屏 - 填充

    我在项目中放置了一个 ContentDialog 用于 Windows 10 上的登录弹出窗口 当我在移动设备上运行此项目时 ContentDialog 未全屏显示 并且该元素周围有最小的填充 在键盘上可见 例如在焦点元素文本框上 键盘和内

随机推荐