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 了。