Inside Qt Series

喜歡程式設計嗎?想要一窺 KDE/Qt 程式設計的奧秘嗎?想要
學習 X Window 上的視窗程式設計嗎?歡迎各類關於 KDE/Qt、Qtopia、Qt/Embedded、QSA、PyQt 等各類心得討論。

版主: AceLan, Franklin

Inside Qt Series

文章訪客 » 週三 9月 16, 2009 12:14 pm

寫了這麼多年的程序,除了留下很多code (其中有很多是garbage)之外,再沒有其它東西,或許我該寫點兒什麼了,寫一些關於我的工作的東西,自己所了解的技術,也把自己在工作過程中新學習的一些東西放在這兒,就算是為了以後做一個參考。
第一個”大項目”,就是準備寫一個系列文章,專門介紹Qt Framework的,這個系列文章不是為Qt新手所寫的,而是寫一個相對來說深入一些的話題,基本思路是,分析Qt的source code,讓我們透過Qt編程技術來了解Qt內部是如何work的。 希望這個系列文章的讀者俱備C/C++的基礎和基本的QT知識,寫過一些QT code,如果能有人從我的文章中獲得些什麼,那我就十分欣慰了。
等等,你說什麼,你還不知道什麼是Qt? 那麼就請到Qt的homepage看看吧: qt.nokia.com
或者看看本站整理的Qt簡介: 《Qt Framework簡介》
好了,讓我門開始吧,請看第一篇:
Inside QT Series (一):Let's go, Starting From the QObject
http://www.insideqt.com/bbs/viewthread.php?tid=4
====================================
聲明:
《Inside Qt Series》專欄文章是Qt核心技術論壇(InsideQt.com)原創技術文章。
本系列專欄文章可隨意轉載,但必須保留本段聲明和每一篇文章的原始地址。
作者保留版權,未經作者同意,不得用於任何商業用途
《Inside Qt Series》專欄文章總索引:
http://www.insideqt.com/bbs/viewthread.php?tid=9
本文原始地址:
http://www.insideqt.com/bbs/viewthread.php?tid=3
前一篇:沒有後一篇: Let's go, Starting From the QObject
http://www.insideqt.com/bbs/viewthread.php?tid=4
訪客
 

文章訪客 » 週三 9月 16, 2009 12:17 pm

QObject這個class是QT對像模型的核心,絕大部分的QT類都是從這個類繼承而來。 這個模型的中心特徵就是一個叫做信號和槽(signal and slot)的機制來實現對象間的通訊,你可以把一個信號和另一個槽通過connect(…)方法連接起來,並可以使用disconnect(…)方法來斷開這種連接,你還可以通過調用blockSignal(…)這個方法來臨時的阻塞信號,

QObject把它們自己組織在對象樹中。 當你創建一個QObject並使用其它對像作為父對象時,這個對象會自動添加到父對象的children() list中。 父對象擁有這個對象,比如,它將在它的析構函數中自動刪除它所有的child對象。 你可以通過findChild()或者findChildren()函數來查找一個對象。

每個對像都有一個對象名稱(objectName())和類名稱(class name),他們都可以通過相應的metaObject對象來獲得。 你還可以通過inherits()方法來判斷一個對象的類是不是從另一個類繼承而來。

當對像被刪除時,它發出destroyed()信號。 你可以捕獲這個信號來避免對QObject的無效引用。

QObject可以通過event()接收事件並且過濾其它對象的事件。 詳細情況請參考installEventFilter()和eventFilter()。

對於每一個實現了信號、槽和屬性的對象來說,Q_OBJECT宏都是必須要加上的。

QObject實現了這麼多功能,那麼,它是如何做到的呢? 讓我們通過它的Source Code來解開這個秘密吧。

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對象的成員數據。

理解這個QObjectPrivate class又是我們理解QT kernel source code的基礎,這個對象包含了每一個QT對像中的數據成員,好了,讓我們首先從理解QObject的數據存儲代碼開始我麼的QT Kernel Source Code之旅。

敬請關注下一節:QObject對像數據存儲
====================================
聲明:
《Inside Qt Series》專欄文章是Qt核心技術論壇(InsideQt.com)原創技術文章。
本系列專欄文章可隨意轉載,但必須保留本段聲明和每一篇文章的原始地址。
作者保留版權,未經作者同意,不得用於任何商業用途
《Inside Qt Series》專欄文章總索引:
http://www.insideqt.com/bbs/viewthread.php?tid=9
本文原始地址:
http://www.insideqt.com/bbs/viewthread.php?tid=4
前一篇: Inside QT Series:序
http://www.insideqt.com/bbs/viewthread.php?tid=3
後一篇: 對像數據存儲(A)
http://www.insideqt.com/bbs/viewthread.php?tid=5
訪客
 

對像數據存儲(A)

文章訪客 » 週三 9月 16, 2009 12:22 pm

前言,為什麼先說這個?

我們知道,在C++中,幾乎每一個類(class)中都需要有一些類的成員變量(class member variable),在通常情況下的做法如下:
class Person
{
private:
string mszName; //姓名
bool mbSex; //性別
int mnAge; //年齡
};

就是在類定義的時候,直接把類成員變量定義在這裡,甚至於,把這些成員變量的存取範圍直接定義成是public的,您是不是這是這樣做的呢?

在QT中,卻幾乎都不是這樣做的,那麼,QT是怎麼做的呢?
幾乎每一個C++的類中都會保存許多的數據,要想讀懂別人寫的C++代碼,就一定需要知道每一個類的的數據是如何存儲的,是什麼含義,否則,我們不可能讀懂別人的C++代碼。 在這裡也就是說,要想讀懂QT的代碼,第一步就必須先搞清楚QT的類成員數據是如何保存的。

為了更容易理解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的時候,我也覺得這種方法很麻煩,但是隨著使用的增多,我開始很喜歡這個方法了,而且,現在我寫的代碼,基本上都會用這種方法。 具體說來,它有如下優點:

* 減少頭文件的依賴性 <br />把具體的數據成員都放到cpp文件中去,這樣,在需要修改數據成員的時候,只需要改cpp文件而不需要頭文件,這樣就可以避免一次因為頭文件的修改而導致所有包含了這個文件的文件全部重新編譯一次,尤其是當這個頭文件是非常底層的頭文件和項目非常龐大的時候,優勢明顯。
同時,也減少了這個頭文件對其它頭文件的依賴性。 可以把只在數據成員中需要用到的在cpp文件中include一次就可以,在頭文件中就可以盡可能的減少include語句
* 增強類的封裝性 <br />這種方法增強了類的封裝性,無法再直接存取類成員變量,而必須寫相應的get/set成員函數來做這些事情。
關於這個問題,仁者見仁,智者見智,每個人都有不同的觀點。 有些人就是喜歡把類成員變量都定義成public的,在使用的時候方便。 只是我個人不喜歡這種方法,當項目變得很大的時候,有非常多的人一起在做這個項目的時候,自己所寫的代碼處於底層有非常多的人需要使用(#include)的時候,這個方法的弊端就充分的體現出來了。

還有,我不喜歡QT 2.x中把數據成員的變量名都定義成只有一個字母,d,看起來很不直觀,尤其是在search的時候,很不方便。 但是,QT kernel中的確就是這麼幹的。
那麼,在最新的QT4裡面是如何實現的呢? 請關注下一節。

前一篇: Inside QT Series (一):Let's go, Starting From the QObject
http://www.insideqt.com/bbs/viewthread.php?tid=4
後一篇: Inside QT Series (三):對像數據存儲(B)
http://www.insideqt.com/bbs/viewthread.php?tid=6
訪客
 

對像數據存儲(B)

文章訪客 » 週三 9月 16, 2009 12:30 pm

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 &amp;dd, QObject *parent = 0);
QObjectData *d_ptr;
}


bjectData *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&lt;Class##Private *&gt;(d_ptr); } \
  inline const Class##Private* d_func() const { return reinterpret_cast&lt;const Class##Private *&gt;(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&lt;QObjectPrivate *&gt;(d_ptr); }
inline const QObjectPrivate* d_func() const
{ return reinterpret_cast&lt;const QObjectPrivate *&gt;(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是什麼關係呢? 他們是如何關聯在一起的呢?
====================================
聲明:
《Inside Qt Series》專欄文章是Qt核心技術論壇(InsideQt.com)原創技術文章。
本系列專欄文章可隨意轉載,但必須保留本段聲明和每一篇文章的原始地址。
作者保留版權,未經作者同意,不得用於任何商業用途
《Inside Qt Series》專欄文章總索引:
http://www.insideqt.com/bbs/viewthread.php?tid=9
本文原始地址:
http://www.insideqt.com/bbs/viewthread.php?tid=6
前一篇: Inside QT Series (二):對像數據存儲(A)
http://www.insideqt.com/bbs/viewthread.php?tid=5
後一篇: Inside QT Series (四):對像數據存儲(C)
http://www.insideqt.com/bbs/viewthread.php?tid=7
====================================
訪客
 

對像數據存儲(C)

文章訪客 » 週三 9月 16, 2009 12:37 pm

接上節,讓我們來看看這個QObjectPrivate和QObject是如何關聯在一起的

代碼: 選擇全部
//--------------------------------
// file name: qobject.cpp
 
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
// ………………………
}
 
QObject::QObject(QObjectPrivate &amp;dd, QObject *parent)
: d_ptr(&amp;dd)
{
// …………………
}


怎麼樣,是不是一目了然呀?
從第一個構造函數可以很清楚的看出來,QObject class中的d_ptr指標將指向一個QObjectPrivate的對象,而QObjectPrivate這個class是從QObjectData繼承出來的。

這第二個構造函數幹什麼用的呢? 從QObject class的定義中,我們可以看到,這第二個構造函數是被定義為protected類別型的,這說明,這個構造函數只能被繼承的class使用,而不能使用這個構造函數來直接構造一個QObject對象,也就是說,如果寫一條下面的語句,編譯的時候是會失敗的,

代碼: 選擇全部
new QObject ( * new QObjectPrivate, NULL )


為了看的更清楚,我們以QWidget這個class為例說明。

QWidget是Qt中所有UI控件的基類別,它直接從QObject繼承而來,

代碼: 選擇全部
class QWidget : public QObject, public QPaintDevice
  {
  Q_OBJECT
  Q_DECLARE_PRIVATE ( QWidget )
  // .....................
  }


我們看一個這個class的構造函數的代碼:

代碼: 選擇全部
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
: QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
d_func()-&gt;init(parent, f);
}


非常清楚,它調用了基類別QObject的保護類別型的構造函數,並且以*new QWidgetPrivate作為第一個參數傳遞進去。 也就是說,基類別(QObject)中的d_ptr指標將會指向一個QWidgetPrivate類別型的對象。
再看QWidgetPrivate這個class的定義:

代碼: 選擇全部
  class QWidgetPrivate : public QObjectPrivate
  {
  Q_DECLARE_PUBLIC ( QWidget )
  // .....................
  }


好了,這就把所有的事情都串聯起來了。
關於QWidget構造函數中的唯一的語句d_func()->init(parent, f)我們注意到在class的定義中有這麼一句話: Q_DECLARE_PRIVATE(QWidget)
我們前面講過這個巨集,當把這個巨集展開之後,就是這樣的:

代碼: 選擇全部
inline QWidgetPrivate* d_func() { return reinterpret_cast&lt;QWidgetPrivate *&gt;(d_ptr); }
inline const QWidgetPrivate* d_func() const
{ return reinterpret_cast&lt;const QWidgetPrivate *&gt;(d_ptr); } \
friend class QWidgetPrivate;


很清楚,它就是把QObject中定義的d_ptr指標轉換為QWidgetPrivate類別型的指標。

小結:
要理解Qt Kernel的code,就必須要知道Qt中每一個Object內部的數據是如何保存的,而Qt沒有像我們平時寫code一樣,把所有的變量直接定義在類別中,所以,不搞清楚這個問題,我們就無法理解一個相應的class。 其實,在Qt4.4中的類別成員數據的保存方法在本質是與Qt2.x中的是一樣的,就是在class中定義一個成員數據的指標,指向成員數據集合對象(這裡是一個QObjectData或者是其派生類別)。 初始化這個成員變量的辦法是定義一個保護類別型的構造函數,然後在派生類別的構造函數new一個派生類別的數據成員,並將這個新對象賦值給QObject的數據指標。 在使用的時候,通過預先定義個巨集裡面的一個inline函數來把數據指標在安全類別型轉換,就可以使用了。

====================================
聲明:
《Inside Qt Series》專欄文章是Qt核心技術論壇(InsideQt.com)原創技術文章。
本系列專欄文章可隨意轉載,但必須保留本段聲明和每一篇文章的原始地址。
作者保留版權,未經作者同意,不得用於任何商業用途
《Inside Qt Series》專欄文章總索引:
http://www.insideqt.com/bbs/viewthread.php?tid=9
本文原始地址:
http://www.insideqt.com/bbs/viewthread.php?tid=7
前一篇: Inside Qt Series (三):對像數據存儲(B)
http://www.insideqt.com/bbs/viewthread.php?tid=6
後一篇: Inside Qt Series (五):元對象系統(Meta-Object System)
http://www.insideqt.com/bbs/viewthread.php?tid=10
====================================
訪客
 


回到 KDE/Qt 程式設計

誰在線上

正在瀏覽這個版面的使用者:沒有註冊會員 和 1 位訪客