深刻了解Qt主要內容來源於Inside Qt系列,本文作了部分刪改,以便於理解。在此向原做者表示感謝!html
QObject這個 class 是 QT 對象模型的核心,關於對象模型能夠閱讀C++對象模型詳解,絕大部分的 QT 類都是從這個類繼承而來。這個模型的中心特徵就是一個叫作信號和槽(signaland slot)的機制來實現對象間的通信,你能夠把一個信號和另外一個槽經過 connect(…) 方法鏈接起來,並可使用disconnect(…) 方法來斷開這種鏈接,你還能夠經過調用blockSignal(…) 這個方法來臨時的阻塞信號。對於信號和槽能夠閱讀Qt 信號和槽函數。安全
QObject 的對象樹機制:ide
當你建立一個 QObject 並使用其它對象做爲父對象時,這個對象會自動添加到父對象的children() list 中。父對象擁有這個對象,好比,它將在它的析構函數中自動刪除它全部的 child對象。你能夠經過 findChild() 或者findChildren()函數來查找一個對象。每一個對象都有一個對象名稱(objectName())和類名稱(class name), 他們均可以經過相應的 metaObject 對象來得到。你還能夠經過 inherits() 方法來判斷一個對象的類是否是從另外一個類繼承而來。當對象被刪除時,它發出destroyed()信號。你能夠捕獲這個信號來避免對QObject的無效引用。QObject能夠經過event()接收事件而且過濾其它對象的事件。詳細狀況請參考installEventFilter()和eventFilter()。對於每個實現了信號、槽和屬性的對象來講,Q_OBJECT 宏都是必需要加上的。函數
QObject 類的實現文件一共有四個:
* qobject.h,QObject class 的基本定義,也是咱們通常定義一個類的頭文件。
* qobject.cpp,QObject class 的實現代碼基本上都在這個文件。
* qobjectdefs.h,這個文件中最重要的東西就是定義了 QMetaObject class,這個class是爲了實現 signal、slot、properties,的核心部分。
* qobject_p.h,這個文件中的 code 是輔助實現QObject class 的,這裏面最重要的東西是定義了一個 QObjectPrivate 類來存儲 QOjbect 對象的成員數據。post
理解這個 QObjectPrivate class 又是咱們理解 QT kernel source code 的基礎,這個對象包含了每個 QT 對象中的數據成員,好了,讓咱們首先從理解 QObject 的數據存儲代碼開始我麼的 QT Kernel Source Code 之旅。學習
咱們知道,在C++中,幾乎每個類(class)中都須要有一些類的成員變量(class member variable),在一般狀況下的作法以下:url
class Person { private: string mszName; // 姓名 bool mbSex; // 性別 int mnAge; // 年齡 };
在QT中,卻幾乎都不是這樣作的,那麼,QT是怎麼作的呢?spa
幾乎每個C++的類中都會保存許多的數據,要想讀懂別人寫的C++代碼,就必定須要知道每個類的的數據是如何存儲的,是什麼含義,不然,咱們不可能讀懂別人的C++代碼。在這裏也就是說,要想讀懂QT的代碼,第一步就必須先搞清楚QT的類成員數據是如何保存的。.net
爲了更容易理解QT是如何定義類成員變量的,咱們先說一下QT 2.x 版本中的類成員變量定義方法,由於在 2.x 中的方法很是容易理解。而後在介紹 QT 4.4 中的類成員變量定義方法。指針
QT 2.x 中的方法
在定義class的時候(在.h文件中),只包含有一個類成員變量,只是定義一個成員數據指針,而後由這個指針指向一個數據成員對象,這個數據成員對象包含全部這個class的成員數據,而後在class的實現文件(.cpp文件)中,定義這個私有數據成員對象。示例代碼以下:
// File name: person.h struct PersonalDataPrivate; // 聲明私有數據成員類型 class Person { public: Person (); // constructor virtual ~Person (); // destructor void setAge(const int); int getAge(); private: PersonalDataPrivate* d; }; //--------------------------------------------------------------------- // File name: person.cpp struct PersonalDataPrivate // 定義私有數據成員類型 { string mszName; // 姓名 bool mbSex; // 性別 int mnAge; // 年齡 }; // constructor Person::Person () { d = new PersonalDataPrivate; }; // destructor Person::~Person () { delete d; }; void Person::setAge(const int age) { if (age != d->mnAge) d->mnAge = age; } int Person::getAge() { return d->mnAge; }
在最初學習QT的時候,我也以爲這種方法很麻煩,可是隨着使用的增多,我開始很喜歡這個方法了,並且,如今我寫的代碼,基本上都會用這種方法。具體說來,它有以下優勢:
* 減小頭文件的依賴性
把具體的數據成員都放到cpp文件中去,這樣,在須要修改數據成員的時候,只須要改cpp文件而不須要頭文件,這樣就能夠避免一次由於頭文件的修改而致使全部包含了這個文件的文件所有從新編譯一次,尤爲是當這個頭文件是很是底層的頭文件和項目很是龐大的時候,優點明顯。
同時,也減小了這個頭文件對其它頭文件的依賴性。能夠把只在數據成員中須要用到的在cpp文件中include一次就能夠,在頭文件中就能夠儘量的減小include語句
* 加強類的封裝性
這種方法加強了類的封裝性,沒法再直接存取類成員變量,而必須寫相應的 get/set 成員函數來作這些事情。
關於這個問題,仁者見仁,智者見智,每一個人都有不一樣的觀點。有些人就是喜歡把類成員變量都定義成public的,在使用的時候方便。只是我我的不喜歡這種方法,當項目變得很大的時候,有很是多的人一塊兒在作這個項目的時候,本身所寫的代碼處於底層有很是多的人須要使用(#include)的時候,這個方法的弊端就充分的體現出來了。
還有,我不喜歡 QT 2.x 中把數據成員的變量名都定義成只有一個字母d,看起來很不直觀,尤爲是在search的時候,很不方便。可是,QT kernel 中的確就是這麼幹的。
QT 4.4.x 中的方法
在 QT 4.4 中,類成員變量定義方法的出發點沒有變化,只是在具體的實現手段上發生了很是大的變化,在 QT 4.4 中,使用了很是多的宏來作事,這憑空的增長了理解 QT source code 的難度,不知道他們是否是從MFC學來的。就連在定義類成員數據變量這件事情上,也大量的使用了宏。
在這個版本中,類成員變量再也不是給每個class都定義一個私有的成員,而是把這一項common的工做放到了最基礎的基類 QObject 中,而後定義了一些相關的方法來存取,好了,讓咱們進入具體的代碼吧。
// file name: qobject.h class QObjectData { public: virtual ~QObjectData() = 0; // 省略 }; class QObject { Q_DECLARE_PRIVATE(QObject) public: QObject(QObject *parent=0); protected: QObject(QObjectPrivate &dd, QObject *parent = 0); QObjectData *d_ptr; }
這些代碼就是在 qobject.h 這個頭文件中的。在 QObject class 的定義中,咱們看到,數據員的定義爲:QObjectData*d_ptr; 定義成 protected 類型的就是要讓全部的派生類均可以存取這個變量,而在外部卻不能夠直接存取這個變量。而 QObjectData 的定義卻放在了這個頭文件中,其目的就是爲了要全部從QObject繼承出來的類的成員變量也都相應的要在QObjectData這個class繼承出 來。而純虛的析構函數又決定了兩件事:
* 這個class不能直接被實例化。換句話說就是,若是你寫了這麼一行代碼,new QObjectData, 這行代碼必定會出錯,compile的時候是沒法過關的。
* 當 delete 這個指針變量的時候,這個指針變量是指向的任意從QObjectData繼承出來的對象的時候,這個對象都能被正確delete,而不會產生錯誤,諸如,內存泄漏之類的。
咱們再來看看這個宏作了什麼,Q_DECLARE_PRIVATE(QObject)
#define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ friend class Class##Private;
這個宏主要是定義了兩個重載的函數,d_func(),做用就是把在QObject這個class中定義的數據成員變量d_ptr安全的轉換成爲每個具 體的class的數據成員類型指針。咱們看一下在QObject這個class中,這個宏展開以後的狀況,就一幕瞭然了。
Q_DECLARE_PRIVATE(QObject)展開後,就是下面的代碼:
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(d_ptr); } inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>;(d_ptr); } \ friend class QObjectPrivate;
宏展開以後,新的問題又來了,這個QObjectPrivate是從哪裏來的?在QObject這個class中,爲何不直接使用QObjectData來數據成員變量的類型?
還記得咱們剛纔說過嗎,QObjectData這個class的析構函數的純虛函數,這就說明這個class是不能實例化的,因此,QObject這個class的成員變量的實際類型,這是從QObjectData繼承出來的,它就是QObjectPrivate !
這個 class 中保存了許多很是重要並且有趣的東西,其中包括 QT 最核心的 signal 和slot 的數據,屬性數據,等等,咱們將會在後面詳細講解,如今咱們來看一下它的定義:
下面就是這個class的定義:
class QObjectPrivate : public QObjectData { Q_DECLARE_PUBLIC(QObject) public: QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); // 省略 }
那麼,這個 QObjectPrivate 和 QObject 是什麼關係呢?他們是如何關聯在一塊兒的呢?
接上節,讓咱們來看看這個 QObjectPrivate 和 QObject 是如何關聯在一塊兒的。
// file name: qobject.cpp QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate) { // ……………………… } QObject::QObject(QObjectPrivate &dd, QObject *parent) : d_ptr(&dd) { // ………………… }
從第一個構造函數能夠很清楚的看出來,QObject class 中的 d_ptr 指針將指向一個 QObjectPrivate 的對象,而QObjectPrivate這個class是從QObjectData繼承出來的。
這第二個構造函數幹什麼用的呢?從 QObject class 的定義中,咱們能夠看到,這第二個構造函數是被定義爲protected 類型的,這說明,這個構造函數只能被繼承的class使用,而不能使用這個構造函數來直接構造一個QObject對象,也就是說,若是寫一條下面的語句, 編譯的時候是會失敗的,
爲了看的更清楚,咱們以QWidget這個class爲例說明。
QWidget是QT中全部UI控件的基類,它直接從QObject繼承而來,
咱們看一個這個class的構造函數的代碼:
很是清楚,它調用了基類QObject的保護類型的構造函數,而且以 *new QWidgetPrivate 做爲第一個參數傳遞進去。也就是說,基類(QObject)中的d_ptr指針將會指向一個QWidgetPrivate類型的對象。
再看QWidgetPrivate這個class的定義:
好了,這就把全部的事情都串聯起來了。
關於QWidget構造函數中的惟一的語句 d_func()->init(parent, f) 咱們注意到在class的定義中有這麼一句話: Q_DECLARE_PRIVATE(QWidget)
咱們前面講過這個宏,當把這個宏展開以後,就是這樣的:
很清楚,它就是把QObject中定義的d_ptr指針轉換爲QWidgetPrivate類型的指針。
小結:
要理解QT Kernel的code,就必需要知道QT中每個Object內部的數據是如何保存的,而QT沒有象咱們平時寫code同樣,把全部的變量直接定義在類中,因此,不搞清楚這個問題,咱們就沒法理解一個相應的class。其實,在QT4.4中的類成員數據的保存方法在本質是與QT2.x中的是同樣的,就是在class中定義一個成員數據的指針,指向成員數據集合對象(這裏是一個QObjectData或者是其派生類)。初始化這個成員變量的辦法是定義一個 保護類型的構造函數,而後在派生類的構造函數new一個派生類的數據成員,並將這個新對象賦值給QObject的數據指針。在使用的時候,經過預先定義個宏裏面的一個inline函數來把數據指針在安全類型轉換,就可使用了。