天天看點

Qt核心剖析:資訊隐藏(2)

下面在上一篇的基礎上,我們進入Qt的源代碼,看看Qt4.x是如何實作 Private Classes 的。

正如前面我們說的,或許你會看到很多類似 Q_D 或者 Q_Q 這類的宏。那麼,我們來試着看一下這樣的代碼:

按照傳統 C++ 的類,如果我們要實作這樣的 getter 和 setter,我們應該使用一個私有變量 _i,然後操作這個變量。按照上一篇說的 Private Class 的做法,我們就要建一個 MyClassPrivateData 這樣的類,然後使用指針對所有的資料操作進行委托。

再來看一個比較 Qt 的例子:

在來看一下 Qt 的實作:

這個例子很簡單,一個使用傳統方法實作,另一個采用了 Qt4.x 的方法。Qt4.x 的方法被稱為 D-Pointer,因為它會使用一個名為 d 的指針,正如上面寫的那個 d_ptr。使用傳統方法,我們需要在 private 裡面寫上所有的私有變量,通常這會讓整個檔案變得很長,更為重要的是,使用者并不需要這些資訊。而使用 D-Pointer 的方法,我們的接口變得很漂亮:再也沒有那一串長長的私有變量了。你不再需要将你的私有變量一起釋出出去,它們就在你的 d 指針裡面。如果你要修改資料類型這些資訊,你也不需要去修改頭檔案,隻需改變私有資料類即可。

需要注意的一點是,與單純的 C++ 類不同,如果你的私有類需要定義 signals 和 slots,就應該把這個定義放在頭檔案中,而不是像上一篇所說的放在 cpp 檔案中。這是因為 qmake 隻會檢測 .h 檔案中的 Q_OBJECT 宏

(這一點大家務必注意)。當然,你不應該把這樣的 private class 放在你的類的同一個頭檔案中,因為這樣做的話就沒有意義了。常見做法是,定義一個 private 的頭檔案,例如使用 myclass_p.h 的命名方式(這也是 Qt 的命名方式)。并且記住,不要把 private 頭檔案放到你釋出的 include 下面!因為這不是你釋出的一部分,它們是私有的。然後,在你的 myclass 頭檔案中,使用

這種前向聲明而不是直接

這種方式。這也是為了避免将私有的頭檔案釋出出去,并且前向聲明可以縮短編譯時間。

在這個類的 private 部分,我們使用了一個 MyClassPrivate 的 const 指針 d_ptr。如果你需要讓這個類的子類也能夠使用這個指針,就應該把這個 d_ptr 放在 protected 部分,正如上面的代碼那樣。并且,我們還加上了 const 關鍵字,來確定它隻能被初始化一次。

下面,我們遇到了一個神奇的宏:Q_DECLARE_PRIVATE。這是幹什麼用的?那麼,我們先來看一下這個宏的展開:

如果你看不大懂,那麼就用我們的 Q_DECLARE_PRIVATE(MyClass) 看看展開之後是什麼吧:

它實際上建立了兩個 inline 的 d_func() 函數,傳回值分别是我們的 d_ptr 指針和 const 指針。另外,它還把 MyClassPrivate 類聲明為 MyClass 的 friend。這樣的話,我們就可以在 MyClass 這個類裡面使用 Q_D(MyClass) 以及 Q_D(const MyClass)。還記得我們最先看到的那段代碼嗎?現在我們來看看這個 Q_D 倒是是何方神聖!

下面還是自己展開一下這個宏,就成了

簡單來說,Qt 為我們把從 d_func() 擷取 MyClassPrivate 指針的代碼給封裝起來了,這樣我們就可以比較面向對象的使用 getter 函數擷取這個指針了。

現在我們已經比較清楚的知道 Qt 是如何使用 D-Pointer 實作我們前面所說的資訊隐藏的了。但是,還有一個問題:如果我們把大部分代碼集中到 MyClassPrivate 裡面,很可能需要讓 MyClassPrivate 的實作通路到 MyClass 的一些東西。現在我們讓主類通過 D-Pointer 通路 MyClassPrivate 的資料,但是怎麼反過來讓 MyClassPrivate 通路主類的資料呢?Qt 也提供了相應的解決方案,那就是 Q_Q 宏,例如:

在 private 類 MyObjectPrivate 中,通過構造函數将主類 MyObject 的指針傳給 q_ptr。然後我們使用類似主類中使用的 Q_DECLARE_PRIVATE 的宏一樣的另外的宏 Q_DECLARE_PUBLIC。這個宏所做的就是讓你能夠通過 Q_Q(Class) 宏使用主類指針。與 D-Pointer 不同,這時候你需要使用的是 Q_Pointer。這兩個是完全相對的,這裡也就不再贅述。

現在我們已經能夠使用比較 Qt 的方式來使用 Private Classes 實作資訊隐藏了。這不僅僅是 Qt 的實作,當然,你也可以不用 Q_D 和 Q_Q,而是使用自己的方式,這些都無關緊要。最主要的是,我們了解了一種 C++ 類的設計思路,這是 Qt 的源代碼教給我們的。

繼續閱讀