这一篇文章介绍QT框架中QT对象类型QObject类型的源代码在设计上的一个比较优秀的设计思想。

QObject类型定义

QObject

直接来看QObject的源代码。为了表达更简洁更直观,这里省略了跟本文无关的各种代码。

如果查看QObject类型定义,可以发现类型里面有很多成员函数,但是除了 d_ptr之外,就没有定义更多的成员变量。当然,没有定义更多成员变量并不等于QObject对象实例中就只有d_ptr这一个数据,实际上由于QObject中定义了虚函数,因此QObject对象实例中还有vptr,也就是指向虚函数表的指针。

现在让我们来讨论这么一个问题,QObject肯定是有内部状态数据的,那么内部状态数据保存在哪儿呢?实际上就是保存在d_ptr指向的QT对象数据对象实例中。d_ptr是QT中的一个范围指针,也就是说当QObject对象被销毁时,d_ptr指向的QT对象数据对象实例也会被销毁掉。

QObjectData

先来看QObjectData类型定义。

这个类型里面定义了一个QObject指针类型的变量q_ptr。在QT对象数据类型有关的各种成员函数中,如果想访问所属于的QObject对象的this指针或者成员函数,可以通过q_ptr这个指针来访问。
也就是说QObject和QObjectData之间相互持有了对方的对象实例的指针,实现了QT对象和QT对象数据的之间的双向访问。

QObjectPrivate

这个类型是QObjectData类型的派生类型。定义了QT对象的一些私有数据。

QT对象的实际对象数据到底是什么

QObject对象

先来看一下QObject类型对象实例中的对象数据是什么。

如果跟踪调试一下,可以看到QObject类型构造函数的源代码。

可以看到在构造函数中一开始就实现了QObject和QObjectPrivate对象实例的双向引用,当然严格来讲是双向的指针指向。

现在新的问题来了,QT框架中有很多具体的QT对象类型,虽然它们都是QObject的直接或间接派生类型,但是各自必然都具有各自类型的独特的私有数据。那么QT框架是如何实现这些类型的对象实例在构造时使用自己类型独特的私有数据的呢?

QThread是一个典型正面例证

直接看QThread类型定义:

然后看一下QThread构造函数:

QThread在构造对象实例时先构造一个QThreadPrivate作为自己的私有数据对象实例,然后让d_ptr指针指向这个私有数据对象实例。
实际上不同的QObject派生类型的对象实例中,都会让d_ptr指向自己独特的私有数据对象实例。也就是QObject的d_ptr和QObjectData的q_ptr这两个指针指向的对象实例之间存在双向相互持有对方指针的情况。

QWidget是一个反面特例

QT框架中除了像QThread这样严格遵守一个QT对象实例只包含一个d_ptr范围指针的情况之外,还有一些特例,比如QWidget就是一个不严格遵守这种情况的特例。

QWidget的类型定义:

看一下QWidget的构造函数:

尽管QWidget除了QObject的d_ptr指针之外还有一个自己的指针data,但是看起来似乎仍然是符合这种不定义具体数据成员之定义指针的这种设计思想的。实际上并非如此。来看一下QPaintDevice类型的定义。

显然QPaintDevice这个类型中除了一个指针之外还有非指针类型的成员变量painters。尽管QPaintDevice并非QObject派生类型,但是肯定是影响到了QWidget的内存布局。

问题根源和解决方案

问题根源

QT框架会使用d_ptr这么一个范围指针,当然QT框架中还有很多类型使用的是原始指针类型。使用这么一个指针类型显然会带来编码上的一些额外的工作量,比如不能直接访问成员变量,而只能通过指针间接访问。
既然使用指针吃力不讨好,为什么QT框架要使用这么一个指针呢?无利不起早,QT框架的设计者不可能吃饱了撑的没事找事。d_ptr必然是解决了一些实际问题才有存在的价值。

先来看问题是怎么产生的。

假定有一个类型定义在butianyunobject.h文件中。

这个类型直接把私有数据成员变量定义在ButianyunObject类型本身,这在一些应用场景下会带来一些问题。

首先是编译问题。
如果有10个cpp文件#include了butianyunobject.h文件,那么一旦对这个.h文件的数据成员变量做那么一丁点修改,在下一次编译这个项目时就会导致这个10个cpp文件全部都必须重新编译。这也就是直接将类型的数据成员变量定义在类型本身的缺点。

其次是依赖问题。
如果说带来的不必要的重新编译问题只是一个项目组内部的技术问题,影响范围有限,那么现在讨论的依赖问题可能影响范围会比较大一点。
考虑这样一个场景:这个代码出现在一个对外公开发布的动态链接库中,而且作为一个公开导出接口,很多客户项目产品中使用了这个公开导出接口。
现在库开发项目组中如果有人为了修复BUG,在某个新版本中修改了一下这个ButianyunObject类型的私有数据成员变量然后新版本中将这个动态链接库和对应的头文件公开发布出去,会产生什么问题呢?或者说会影响到客户项目产品码?
答案是一定会影响到使用新版本的客户项目产品。一个类型的数据成员变量修改之后,会导致类型的对象实例的内存布局发生变化,这意味着所有想使用新版本的客户项目产品的软件必须使用新的头文件重新编译,而无法直接使用新版本的动态链接库去替换旧版本的动态链接库。

解决方案

然后来看解决方案,或者说设计思路。
如果将ButianyunObject类型这么来定义就可以避免这些问题。

也就是将类型的私有数据成员变量全部抽取到一个私有数据类型中,然后将这个私有数据类型定义在.cpp文件中,而不是.h文件中。在对外公开的数据类型中只定义了一个指针类型的变量d_ptr, d_ptr指向实际的私有数据对象实例。
这样就算是修改私有数据成员变量,也不会导致对外公开接口发生任何变化,那么也就不会引起内部编译问题。在各种公开接口的内存布局没有发生变化的情况下,如果没有其它影响因素,那么也就不会引起客户项目产品软件的重新编译问题,也就是说客户项目组可能只需替换成新版本的动态链接库即可。

间接的设计思想

这种设计思想就是所说的C++ PIMPL。PIMPL=Pointer to Implement,也就是指向具体实现的指针,说白了就是使用指向对象的具体私有数据对象实例的指针代替直接定义私有数据本身。
QT框架中实际上大量使用了PIMPL设计思想。

这篇文章也可以说是受到了知乎上对C++ PIMPL的讨论的帖子的启发才编写的。
为什么C++ PImpl 的实现类成员函数的参数类型需要是回溯引用?

下面再进一步进行抽象思考,再拔高一个层次,所谓PIMPL,就是间接的思想的具体体现或者具体应用。实际上C/C++的指针类型的“指向”的含义本来自带一层“间接”的意思。
间接的思想是所有设计模式的最本质最根本的底层设计思想和底层思考逻辑。可以这么讲,没有哪一个设计模式不是对间接的思想的具体体现。

当然再拔高了讲,大部分软件架构也都是在某一个层面上充分应用了间接的思想。一般的思考逻辑也是将现状抽象一下,然后在某个点上使用一个软件框架来实现原来直接用几行代码去做的事情,也就是间接的使用一个更复杂的抽象逻辑框架去解决原来直接去做带来的问题。
总结

间接的思想带来的另外一个天然的好处就是自然而然的实现了关注点分离,对于公开接口的使用者而言,根本看不到一个框架或者类的内部实现细节。

当然,这里分析的只是QT框架核心模块的很少的一点源代码片段而已。有兴趣想深入学习QT原理的朋友可以关注一下这个课程:QT5原理与源码分析视频课程。
如果读者对如何快速全面了解QT框架感兴趣,可以看一下这篇文章:

bird:快速全面了解QT软件界面开发技术

如果读者对如何学习QT框架有兴趣,可以看一下这篇文章:

bird:如何学习C/C++/QT软件开发技术

如果您认为这篇文章对您有所帮助,请您一定立即点赞+喜欢+收藏,本文作者将能从您的点赞+喜欢+收藏中获取到创作新的好文章的动力。如果您认为作者写的文章还有一些参考价值,您也可以关注这篇文章的作者。

QT原理与源码分析之QT对象类型QObject源码中的间接的设计思想的更多相关文章

  1. JVM源码分析之Java对象头实现

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...

  2. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  3. Shiro源码分析之SecurityManager对象获取

    目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @   上篇文章Shiro源码分析之获 ...

  4. Flask框架 (四)—— 请求上下文源码分析、g对象、第三方插件(flask_session、flask_script、wtforms)、信号

    Flask框架 (四)—— 请求上下文源码分析.g对象.第三方插件(flask_session.flask_script.wtforms).信号 目录 请求上下文源码分析.g对象.第三方插件(flas ...

  5. jQuery源码分析系列(36) : Ajax - 类型转化器

    什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的 ...

  6. TeamTalk源码分析(十一) —— pc客户端源码分析

           --写在前面的话  在要不要写这篇文章的纠结中挣扎了好久,就我个人而已,我接触windows编程,已经六七个年头了,尤其是在我读研的三年内,基本心思都是花在学习和研究windows程序上 ...

  7. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  8. Springboot源码分析之代理对象内嵌调用

    摘要: 关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法.首先在这儿我要声明事务直接的嵌套调 ...

  9. Netty源码分析 (七)----- read过程 源码分析

    在上一篇文章中,我们分析了processSelectedKey这个方法中的accept过程,本文将分析一下work线程中的read过程. private static void processSele ...

  10. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

随机推荐

  1. [oeasy]python0009 - 设置断点_break_point

    ​ 调试程序 回忆上次内容 ​py​​ 的程序是按照顺序执行的 是一行行挨排解释执行的 程序并不是数量越多越好 kpi也在不断演化 ​ 编辑 写的代码越多 出现的bug就越多 那什么是bug呢? 如何 ...

  2. LeetCode860. 柠檬水找零

    题目链接:https://leetcode.cn/problems/lemonade-change/description/ 题目叙述: 在柠檬水摊上,每一杯柠檬水的售价为 5 美元.顾客排队购买你的 ...

  3. 【教程】重启Windows文件资源管理器

    [教程]重启Windows文件资源管理器 打开任务管理器 以下方法任选其一: 方法一 :组合键 Ctrl + Shift + ESC (个人推荐) 方法二 :组合键 Win + X (或右键Windo ...

  4. java8interface的新特性:default,static,funcation

    default:默认方法 在类接口中可以直接定义的方法,实现接口的类可以直接使用 使用案例: public interface MyInterface { default void display() ...

  5. 【Linux】快速文件交互 lrzsz

    首先需要安装依赖: yum install -y lrzsz 没有此依赖,Linux提示找不到命令: [root@localhost ~]# rz -bash: rz: 未找到命令 [root@loc ...

  6. Ubuntu18.04 系统环境下 vscode中忽略pylint某些错误或警告

    相关: ubuntu18.04系统环境下使用vs code安装pylint检查python的代码错误 ====================================== 假设已经在前文(ht ...

  7. xshell打开vim后颜色异常——xshell连接ubuntu打开vim后界面覆盖一层绿色

    参考原文: https://blog.csdn.net/Blank_Shen/article/details/106527312 =================================== ...

  8. 作为电脑屏幕的补光灯,到底是应该选Led灯还是荧光灯

    现在的台灯灯具市场基本被Led灯给霸占,这就无形之中要大家买台灯的时候只能选择Led等,我也是如此,手上有一款20年前上高中时候的"孩视宝"荧光灯的台灯,然后还有一款刚刚购入的Le ...

  9. 纪念IE浏览器退役,哈哈哈!!!

    网址: https://haokan.baidu.com/v?pd=wisenatural&vid=16024148879625055169 ========================= ...

  10. 一种很变态但有效的DDD建模沟通方式

    本文书接上回<这就是为什么你学不会DDD>,关注公众号(老肖想当外语大佬)获取信息: 最新文章更新: DDD框架源码(.NET.Java双平台): 加群畅聊,建模分析.技术实现交流: 视频 ...