1. 程式人生 > >QT---Q_D和d指標的概念1

QT---Q_D和d指標的概念1

Qt中的Q_D巨集和d指標

1 _ZTS7QObject

 

一、Q_D的在檔案中的提法

  Q_D的設定意在方便地獲取私有類指標,檔案為qglobal.h。下面的##是巨集定義的連字元。假設類名是A,那麼A##Private翻譯過來就是APrivate。

1 #define Q_D(Class) Class##Private * const d = d_func()

  d_func()函式如下實現:

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;

  這裡的d_func()雖然在巨集裡面,但是如果代入具體的型別,這裡面就變成了以Class##Private的私有類指標為返回值,以func為函式名的函式。這裡的qGetPtrHelper是

1 template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }

  這個模板函式裡面裡的T要套入某個具體的類。上面的那個Q_DECLARE_PRIVATE裡面的就成了這個模板類的一個呼叫。這個模板類就是把這個類指標轉換成了靜態的。有了上面的這三段程式碼,如果想在某個類A裡面宣告一個私有類,直接來一個Q_D(A),再Q_DECLARE_PRIVATE(A)就可以了。

二、巨集和模板的展開

  展開前一個巨集和後一個巨集的一部分,成了

1 #define Q_D(A) APrivate *const d= d_func()
1 inline APrivate* d_func() { return reinterpret_cast<APrivate *>(qGetPtrHelper((d_ptr));}

  上面這個函式qGetPtrHelper呼叫的輸入值是QObject類裡面的一個成員變數,d_ptr指標,定義如下:

1     QScopedPointer<QObjectData> d_ptr;

   根據模板函式呼叫返回的仍然是一個QScopedPointer<QObjectData>型別的變數,也就是一個指向QObjectData型別的智慧限域指標。最後呼叫reinterpret_cast重新解釋前面得到的指標,把它變成指向APrivate型別的。

  這裡的QObjectData的定義也在QObject裡面

複製程式碼

 1 class Q_CORE_EXPORT QObjectData {
 2 public:
 3     virtual ~QObjectData() = 0;
 4     QObject *q_ptr;
 5     QObject *parent;
 6     QObjectList children;
 7 
 8     uint isWidget : 1;
 9     uint blockSig : 1;
10     uint wasDeleted : 1;
11     uint isDeletingChildren : 1;
12     uint sendChildEvents : 1;
13     uint receiveChildEvents : 1;
14     uint isWindow : 1; //for QWindow
15     uint unused : 25;
16     int postedEvents;
17     QDynamicMetaObjectData *metaObject;
18     QMetaObject *dynamicMetaObject() const;
19 };

複製程式碼

  至於QScopedPointer,有點複雜,核心思想是一個不需要自己銷燬的指標。

三、繼承和呼叫

  自己寫一個類MyQFileSystemModel繼承QFileSystemModel,在MyQFileSystemModel中使用Q_D巨集,會出現錯誤:

複製程式碼

 1 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h: In constructor 'MyQFileSystemModel::MyQFileSystemModel()':
 2 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1002:28: error: 'QFileSystemModelPrivate* QFileSystemModel::d_func()' is private
 3      inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
 4                             ^
 5 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtWidgets/qfilesystemmodel.h:152:5: note: in expansion of macro 'Q_DECLARE_PRIVATE'
 6      Q_DECLARE_PRIVATE(QFileSystemModel)
 7      ^
 8 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:54: error: within this context
 9  #define Q_D(Class) Class##Private * const d = d_func()
10                                                       ^
11 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'
12      Q_D(const QFileSystemModel);
13      ^
14 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include/QtCore/qglobal.h:1016:43: warning: unused variable 'd' [-Wunused-variable]
15  #define Q_D(Class) Class##Private * const d = d_func()
16                                            ^
17 ..\student\myqfilesystemmodel.cpp:5:5: note: in expansion of macro 'Q_D'

複製程式碼

   意思是,d_func()是QFileSystemModel這個類裡面的私有函式,對於c++的私有函式,子類是不能夠繼承的。

四、q指標

  寫一個MyQFileSystemModel的私有類MyQFileSystemModelPrivate,就可以使用Q_Q巨集,從私有類引用對應的公有類了。

複製程式碼

 1 #ifndef MYQFILESYSTEMMODEL_P_H
 2 #define MYQFILESYSTEMMODEL_P_H
 3 #include <myqfilesystemmodel.h>
 4 class MyQFileSystemModelPrivate;
 5 class MyQFileSystemModel;
 6 QT_BEGIN_NAMESPACE
 7 class MyQFileSystemModelPrivate: public QFileSystemModelPrivate
 8 {
 9 public:
10     //MyQFileSystemModelPrivate(MyQFileSystemModel * parent):q_ptr(parent){}
11 public:
12     Q_DECLARE_PUBLIC(MyQFileSystemModel)
13     MyQFileSystemModel *q_ptr;
14 };
15 QT_END_NAMESPACE
16 #endif // MYQFILESYSTEMMODEL_P_H

複製程式碼

  這裡面需要尤其注意的是,MyQFileSystemModel *q_ptr;這一行是不能少的。c++的static_cast是對被轉換的型別有限制的。如果B類繼承了A類,那麼從B類轉換成A類是完全沒有問題的。但是,如果想把A類轉換成B類,就要求B類裡面,問題就出現了,A類可能沒有B類那麼豐滿,轉換出來的類可能是殘廢的,所以一般情況下,這種轉換是不能成功的。除非:B類裡面包含了一個指向A類的指標。如果把q_ptr指標宣告去掉了,會報這個錯誤:

1 C:\Qt\Qt5.9.2\5.9.2\mingw53_32\include\QtCore\qglobal.h:1012: error: invalid static_cast from type 'QObject*' to type 'MyQFileSystemModel*'
2      inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
3                                                                ^

五、私有類函式呼叫

  Qt中的公有類和私有類關係密切,私有類的函式是不能夠直接使用的。要是想用,一定要通過其他類呼叫。在Qt編譯的時候,QFileSystemModel.h是一堆宣告,會匯出很多函式到QtWidget.dll裡面去。值得注意的是,這裡匯出的只是這個檔案裡所宣告的類、函式和變數,而不會匯出只在私有類標頭檔案QFileSystemModel_p.h裡面宣告過的函式和變數。

  當然,編譯的時候,也會引用QFileSystemModelPrivate.h和QFileSystemModel.cpp裡的宣告以及定義。一般情況下,我們只是Qt的使用者,在windows系統下,只要下載安裝就好,不需要自己再編譯了。但是當我們想使用私有類做一些更深入的定製的時候,希望能夠調直接呼叫私有類的成員。這時候,如果只是include了.h檔案,就會報undefined reference錯誤。也就是說,編譯能過,連結過不了,找不到私有類成員。

   解決的方法就是將對應的cpp檔案也包含進工程目錄裡面,但這時候會有新的問題出現。有些類的實現已經編譯到庫函式裡面了,這時候又在cpp檔案重新實現,會報警告:redeclared without dllimport attribute。只需要把cpp檔案中的實現函式刪除掉就可以了。

  雖然通過上面的方法,可以實現私有類的使用,但是值得注意的是,已經編譯好的二進位制檔案dll裡面的同名私有函式還在起著作用。還是用QFileSystemModel為例,它已經被Qt編譯好放到了QWidget.dll裡面了。在此dll檔案裡也會有QFileSystemModelPrivate的函式(只是不會有直接指向外部的宣告,外部不能直接連結引用而已),否則私有類就完全沒用了。當新的工程要使用到QFileSystemModel,而QFileSystemModelPrivate被間接地引用,使用的版本就是之前編譯好的成品。當新的工程要直接呼叫私有類函式的時候,才是用的新的版本。

  用Dependency開啟Qt5Widgets.dll看到私有類的函式名形式如下:

1 _ZN16QFileSystemModelC1ER23QFileSystemModelPrivateP7QObject

  這可能只是一個建構函式,在Qt5Widgets.dll裡只有兩個上面這種QFileSystemModelPrivate的函式,可見大多數私有類的函式沒有匯出。而直接用本節方法編譯出來的QFileSystemModelPrivate.dll裡面的私有成員函式形式如下:  

1 _ZNK23QFileSystemModelPrivate4nodeERK11QModelIndex

  代表形參是QModelIndex的私有類函式node,像這樣的函式還有很多,它們與私有類的.h檔案能夠對應起來。