一、回顧
使用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這種代碼組織方式。
補充: