Qt d-pointer & q-pointer
每次看 Qt source code 都不知道 d-> 跟 q-> 到底在幹嘛,翻到 D-Pointer 這篇解釋就來看一下。但是我原本不是要追究這個的ㄚㄚㄚ……
d-pointer pattern
d-pointer 是個 design pattern,也叫 Opaque pointer 或 Pimpl idiom。它分開給外面看的公開 class interface 跟實作的 class。做法是在公開 class 放個實作 class 的 pointer,公開 class 各 function 只是 call implementation class 的 function。公開 class 中跟實作相關的 member 只有那個 pointer,所以 size 會固定是 pointer 的 size。老覺得在哪看過類似的東西……
Qt 用 d-pointer 主要是為了保持 binary compatibility。
Binary Compatibility
有 binary compatibility 表示 application 在以舊版 library compile 後,新版 library binary 可以直接取代舊版 library 而不需要重新 compile application。要達到 binary compatibility 就不能改變已經公開出去的 C++ class 的 size 或 memory layout,即不能增加 member 或者更改 function 跟 member 的順序。如果改了,application 認定的 library class size 或 memory layout 跟新版 library 不同,會把 memory 踩爛掉。
Qt 使用 d-pointer 達到不修改既有公開 class 還能增加 feature 或修改實作細節。
Qt 的實作
基於保持 binary compatibility,Qt 幾乎所有 class 都用 d-pointer 指向真正實作的 class。實作 class 叫 QXXXPrivate,例如 QWidget 就有 QWidgetPrivate,通常定義在 xxx_p.h,例如 qwidget_p.h。QXXX 以及 QXXXPrivate 的實作會寫在 qxxx.cpp。
為了減少 memory allocation 的次數,QXXXPrivate 的 class 也會有繼承關係。其繼承關係會依循一般 class 的繼承關係,例如 QLabel 繼承 QFrame,QLabelPrivate 就會繼承 QFramePrivate。QXXXPrivate 大多繼承自 QObjectPrivate。
以下 code 擷取自 Qt 5.7.0。
d-pointer & q-pointer
1 | class Q_CORE_EXPORT QObject |
好啦~上面是 d-pointer,QObjectPrivate 繼承 QObjectData,而 QObject 有 d_ptr 指向 QObjectData。
有時候實作 class 需要使用公開 class,因此實作 class 會存公開 class 的 pointer,稱為 q-pointer。如上,QObjectData 有 QObject 的 pointer q_ptr。
在 Qt 裡 d-pointer 跟 q-pointer 基本上是繼承自 base class,不過也有例外。
Q_D() & Q_Q () macro
qtbase/src/corelib/global/qglobal.h 定義了這些 function 跟 macro:
1 | template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; } |
在 class 內會用 Q_DECLARE_PRIVATE(),以 QStandardItem 為例:
1 | class Q_GUI_EXPORT QStandardItem |
QStandardItem 裡有兩個 inline 的 private function 分別為 QStandardItemPrivate* d_func() 以及 const QStandardItemPrivate* d_fun() const,並且 QStandardItemPrivate 是 QStandardItem 的 friend class,可以 access QStandardItem 的 private member。constructor 生了 QStandardItemPrivate 並將 d_ptr 指過去。
依據 macro 的定義,d 是 QStandardItemPrivate*,d_func() 將 qGetPtrHelper(d_ptr) 轉型成 QStandardItemPrivate* 丟出來。d_ptr 是個 QScopedPointer<QStandardItemPrivate>,qGetPtrHelper() 將 smart pointer 轉成單純的 pointer(參考這篇)。還沒有看得很懂怎麼轉的,不過先這樣理解吧… 總之 d 是指向 construct 時塞進來的 QStandardItemPrivate 的 pointer。所以最後 QStandardItem::setChild() 是用 QStandardItemPrivate::setChild() 實際做事。
反過來的 Q_Q() 比較簡單。這 macro 的名字真好笑(離題
1 | class QStandardItemPrivate |
透過 q_func() 拿到的 q 是轉型成 QStandardItem* 的 pointer。
這個例子裡,因為 QStandardItem 跟 QStandardItemPrivate 沒有繼承別人,它們自己分別記錄了 d_ptr 跟 q_ptr,所以 d_func() 跟 q_func() 有沒有轉型根本沒差。但其他繼承 QObjectPrivate 的 class,q_ptr 是從 QObjectData 來的,type 是 QObject*,如果要直接使用 sub class 的 member 就得轉型,d_ptr 同理。除非用 virtual function 做多型,不過這個狀況用 virtual function 很怪,因為這樣 base class QObject 得有所有 sub class 會用到的 interface,這邊顯然不是這個意思,用 virtual function 不合理。
這樣看過之後,對 Qt source code 的結構比較理解了,才不會每次看到 Q_D() 啊 Q_Q() 啊都想 Q_Q 了。