一、回顾
使用qt2年多了,但是还是觉得很陌生,总是会被qt搞的很紧张,有时候当我自信满满的打开帮助文档,搜索某个已知的类时,由于笔误敲错了一个字母而出现了另外一个类,不过奇怪的是还真有这么一个类,哎!!!我怎么都不知道呢!看来qt的东西还真不是一般的多,随便一个笔误都可能发现新的东西。特别是qt现在已经发布了qt5.7版本,相对于qt4的时代那真是天差地别,就我个人而言,我现在用的是qt5.6.1-1,因为qt5.6版本官方声称维护2年的时间。qt5.6取消了webkit模块,如果需要使用可以自行编译,我自己也对qt5.6.1-1的整个模块进行了编译,有需要的小伙伴可以参考msvc2013编译qt5.6源码
本文主要是想讲述qt的一个代码书写方式,当然了这样说有些大,其实就是qt他的源码在分层上是把成员都单独拉到一个filename_p.h的头文件里,这样做的有好处也有坏处,下面是我自己的观点:
优点 | 缺点 | |
qt | 1、庞大的代码容易管理 2、成员的私有实现和具体对外开放的接口分离 | 1、阅读性差 2、可扩展性差 |
表1 qt优缺点
上述缺点都是我个人认为,其中可扩展性差不是说qt不能扩展,而是扩展的时候有一定限制,因为qt的一个功能实现的最基本单元就是filename.h、filename.cpp和filename_p.h,通常情况下我们只能拿到filename.h,而私有的filename_p.h文件拿不到,所以不能进行对齐进行扩展,这也导致了一些操作不能很好的而进行,除非qt源码给我们了相关接口,否则我们是很难去维护私有的filename_p.h的实现,比如:qt的qcharts模块,这个模块是qt5.7才正式开源的模块,当然了有兴趣的同学可以自己手动在自己的当前qt模块下编译qtcharts,然后将开发所需要的库都加入到自己当前的qt版本中。
为什么说提到qcharts模块呢,是因为我之前看过大概的实现原理,他也是一贯的qt代码作风,那就是不容易阅读,每一个对外暴露的接口,仅仅是接口而已,而他具体的实现都是隐藏在另外一个没有暴露给用户的接口里,如下图1所示,QBarSeries是对外开放的接口,但是其具体实现是在BarSeries内完成的,这样就导致了我们只能对QBarSeries进行重写,而不能修改BarSeries类,或者对齐进行重写。
图1 QBarSeries帮助文档
呵呵呵。。。说的有些多了,可能喜欢qt的人会来骂我,以上完全是我个人的理解,有不对的地方请指正。其实我个人还是挺喜欢qt的,最起码在封装上我觉得很好,接口的命名上也很容易理解,比较容易上手,对于一个做C++界面工作的开发来说,算是一个不错的选择。
下面进入本片文章的主题,本片文章我主要是想分析下qt的私有实现结构,其实也就是我们所谓的impl实现原则。
二、思路
说起qt的代码管理,我觉着真的非常好,毕竟是第一个庞大的库,如果没有一个优秀的代码结构,对于C++这样编译性的语言,那真的是一个噩梦,多么优秀的程序员可能一天就处于编译代码的等待中。。。
qt在类的管理上,讲成员函数单独封装到了一个对于的_p.h的头文件中,然后可能还是提供相应的私有接口,为什么说私有呢,因为我们拿不到,但是qt的类中却可以拿到,这个在下一节中我会做一些简单的分析。
qt代码分工:filename.h、filename.cpp和filename_p.h,而这3个文件是用一组宏类关联起来,并使用,有一定的封装性。后续文章中,filename.h中的类我就用Class描述,filename_p.h中的类我就用ClassPrivate描述。
三、关键宏
- Q_DECLARE_PUBLIC
- Q_DECLARE_PRIVATE
- Q_D
- Q_Q
1、Q_DECLARE_PUBLIC
这个宏是用来在filename_p.h类中使用得,为的就是可以拿到一个Class的类的一个指针,并且它是Class的友元类,Class类是一个泛指,指的是filename.h纵的类。
1 #define Q_DECLARE_PUBLIC(Class) \ 2 inline Class* q_func() { return static_cast<Class *>(q_ptr); } \ 3 inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \ 4 friend class Class;
2、Q_DECLARE_PRIVATE
这个宏是在filename.h类中使用,和Q_DECLARE_PUBLIC宏是对于的,它是ClassPrivate类的友元,并且可以拿到一个ClassPrivate的指针
1 #define Q_DECLARE_PRIVATE(Class) \ 2 inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ 3 inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ 4 friend class Class##Private;
3、Q_D和Q_Q
这两个宏就比较好理解了,仅仅是快捷的拿到一个指针,关于这个指针是怎么声明?在哪儿声明?后续都会提到
1 #define Q_D(Class) Class##Private * const d = d_func()2 #define Q_Q(Class) Class * const q = q_func()
四、结构说明
首先我先来回答一下,第三节中第三小节所提到的问题,关于Class和ClassPrivate这两个指针存储的地方,就是他们分别的功能基类,而且必须是保护或者是公有成员。在后续的子类中都不需要再声明该类指针
作为父类都必须提供至少两个构造函数,一个是用来声明对象自己的,一个是用来初始化子类的,这样说可能会有点儿不好理解,那么我就把qwidget的源码搬上来做以说明
图1 QWidget公有构造函数
图2 QWidget保护构造函数
图1和图2分别是QWidget的两个构造函数,只是访问权限不一样,保护函数只能在子类中访问,那么同学们是不是有些明白了,基类的ClassPrivate就是通过这个保护的构造方法类初始化,子类传递给父类的ClassPrivate指针,能赋值给父类的成员指针,那么这个指针肯定有父子关系的,那么又一个关键点浮出水面,那就是ClassPrivate也是可以继承的,这样很好的达到了私有数据继承的问题,如图3就是调用了子类的私有结构来初始化父类私有结构
图3 子类构造函数
零散的说了这么多,qt二进制兼容性也说了个大概,只是没有系统的demo,那么下面我贴一个完整的测试头文件夹,关于demo,后续我会给一个完整的
1 struct BasicPlotPrivate; 2 //基类 保存私有结构指针 并提供一个保护的构造函数 3 class BasicPlot : public QWidget 4 , public PlotCallback 5 { 6 Q_OBJECT 7 Q_DECLARE_PRIVATE(BasicPlot) 8 9 protected: 10 BasicPlot(QWidget * parent = nullptr); 11 public: 12 ~BasicPlot(); 13 14 protected: 15 BasicPlot(BasicPlotPrivate & pd, QWidget * parent = nullptr); 16 17 protected: 18 QScopedPointer<BasicPlotPrivate> d_ptr; 19 }; 20 //基类的私有结构 保存一个基类指针 21 struct BasicPlotPrivate 22 { 23 Q_DECLARE_PUBLIC(BasicPlot) 24 25 BasicPlot * q_ptr; 26 }; 27 //子类 不需要声明指针 公有构造函数实现类似于图3 28 class FinancialPlot : public BasicPlot 29 { 30 Q_OBJECT 31 Q_DECLARE_PRIVATE(FinancialPlot) 32 33 protected: 34 FinancialPlot(QWidget * parent = nullptr); 35 36 public: 37 ~FinancialPlot(); 38 39 protected: 40 FinancialPlot(FinancialPlotPrivate & pd, QWidget * parent = nullptr); 41 }; 42 //子类私有结构 继承与父类私有结构 43 struct FinancialPlotPrivate : public BasicPlotPrivate 44 { 45 FinancialPlotPrivate() : BasicPlotPrivate(){} 46 Q_DECLARE_PUBLIC(FinancialPlot) 47 };
五、示例下载
关于示例下载,我将会在下一节qcustomplot使用分享中提供一份demo,而这个dmeo的实现就是使用的qt这种代码组织方式。
补充: