詳解 Qt 源碼分析 QObject
Qt 源碼分析 QObject是本文要介紹的內容,很詳細的去解析,先來看內容。Qt的QObject
1.試驗代碼:
- #include <QApplication>
- #include <QtCore>
- #include <QtGui>
- int main(int argc, char *argv[])
- {
- QApplication app(argc, argv);
- int size = sizeof(QObject);
- QPushButton* quit = new QPushButton("Quit");
- delete quit;
- return app.exec();
- }
QObject是Qt類體系的唯一基類,就象MFC中的CObject和Dephi中的TObject,是Qt各種功能的源頭活水,因此Qt源碼分析的***節就放在這個QObject上
- int size = sizeof(QObject);
QObject的大小是8,除了虛函數表指針需要的4個字節以外,另外的4個字節是:
- QObjectData *d_ptr;
QObject中的數據被封裝在QObjectData類中了,為什么要封裝數據呢?
原因是Qt中有一個很重要的設計模式就是句柄實體模式,也就是以QObject為基類的類一般都是句柄類,一般只有一個指針指向一個實體類,在實體類中保存全部的數據
而且一般情況下這個指針還是私有的,方便以后修改句柄類的實現細節
因此,也可以說和句柄類繼承關系平行的也有一套實體類派生體系,因此,準確的說,Qt的基類其實有兩個,一個是QObject,這是句柄類的唯一基類,另一個是QObjectData,這是實體
類的基類
- QObjectData類定義如下:
- class QObjectData {
- public:
- virtual ~QObjectData() = 0;
- QObject *q_ptr;
- QObject *parent;
- QObjectList children;
- uint isWidget : 1;
- uint pendTimer : 1;
- uint blockSig : 1;
- uint wasDeleted : 1;
- uint ownObjectName : 1;
- uint sendChildEvents : 1;
- uint receiveChildEvents : 1;
- uint unused : 25;
- int postedEvents;
- #ifdef QT3_SUPPORT
- int postedChildInsertedEvents;
- #else
- int reserved;
- #endif
- };
- QObject *q_ptr;
這個指針指向實體類對應的句柄類,這和上面的代碼
- QObjectData *d_ptr;
遙相呼應,使得句柄類和實體類可以雙向的引用,為什么是這樣的命名方式呢?可能q指的是Qt接口類,d指的是Data數據類,這當然是猜測了,但是或許可以方便你記憶,在Qt中,
這兩個指針名字是非常重要的,必須記住
但是僅僅如此還是不容易使用這兩個指針,因為它們都是基類的類型,難道每次使用都要類型轉換嗎?為了簡單起見,Qt在這里聲明了兩個宏
- #define Q_DECLARE_PRIVATE(Class) \
- inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
- inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
- friend class Class##Private;
- #define Q_DECLARE_PUBLIC(Class) \
- inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
- inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
- friend class Class;
只要在類的頭文件中使用這兩個宏,就可以通過函數直接得到實體類和句柄類的實際類型了,而且這里還聲明了友元,使得數據類和句柄類連訪問權限也不用顧忌了
而且為了cpp文件中調用的方便,更是直接聲明了以下兩個宏
- #define Q_D(Class) Class##Private * const d = d_func()
- #define Q_Q(Class) Class * const q = q_func()
好了,使用起來倒是方便了,但是以后局部變量可千萬不能聲明為d和q了
這里的d_func和q_func函數是非常常用的函數,可以理解為一個是得到數據類,一個是得到Qt接口類
- QObject *parent;
這里指向QObject的父類
- QObjectList children;
這里指向QObject相關的子類列表
這確實是個大膽的設計,如果系統中產生了1000000個QObject實例(對于大的系統,這個數字很容易達到吧),每個QObject子類平均下來是100(這個數字可能大了),
光這些指針的開銷就有1000000*100*4=400M,是夠恐怖的,如果我們必須在靈活性和運行開銷之間做一個選擇的話,無疑Qt選擇了前者,對此我也很難評論其中的優劣,
還是祈求越來越強的硬件水平和Qt這么多年來得到的赫赫威名保佑我們根本就沒有這個問題吧,呵呵
總之,Qt確實在內存中保存了所有類實例的樹型結構
- uint isWidget : 1;
- uint pendTimer : 1;
- uint blockSig : 1;
- uint wasDeleted : 1;
- uint ownObjectName : 1;
- uint sendChildEvents : 1;
- uint receiveChildEvents : 1;
- uint unused : 25;
這些代碼就簡單了,主要是一些標記位,為了節省內存開銷,這里采用了位域的語法,還保留了25位為unused,留做以后的擴充
- #ifdef QT3_SUPPORT
- int postedChildInsertedEvents;
- #else
- int reserved;
- #endif
這里或許是為了兼容Qt3下序列化的數據吧,即使沒有定義QT3_SUPPORT,還是保留了一個數據reserved,以保證整個QObjectData的大小不變
#p#
具體看一個例子吧,對這種句柄實體模式加深認識,這就是Qt中的按鈕類QPushButton
- QPushButton的句柄類派生關系是:
- QObject
- QWidget
- QAbstractButton
- QPushButton
- QPushButton的實體類派生關系是:
- QObjectData
- QObjectPrivate
- QWidgetPrivate
- QAbstractButtonPrivate
- QPushButtonPrivate
可以看出,這里確實是一個平行體系,只不過實體類派生關系中多了一個QObjectPrivate,這個類封裝了線程處理,信號和槽機制等具體的實現,可以說它才是Qt實體類中
真正起作用的基類,而QObjectData不過是一層淺淺的數據封裝而已
先不忙了解QObjectPrivate類中的接口和實現,我們先看看在Qt中,句柄類和實體類這兩條體系是如何構造的?
- QPushButton* quit = new QPushButton("Quit");
創建一個Qt的按鈕,簡簡單單一行代碼,其實背后大有玄機
- QPushButton::QPushButton(const QString &text, QWidget *parent)
- : QAbstractButton(*new QPushButtonPrivate, parent)
首先QPushButton的構造函數中調用了QAbstractButton的構造函數,同時馬上new出來一個QPushButtonPrivate實體類,然后把指針轉換為引用傳遞給QAbstractButton
- QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
- : QWidget(dd, parent, 0)
QAbstractButton的構造函數中繼續調用基類QWidget的構造函數,同時把QPushButtonPrivate實體類指針繼續傳給基類
- QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)
- : QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice()
QWidget繼續坐著同樣的事情
- QObject::QObject(QObjectPrivate &dd, QObject *parent)
- : d_ptr(&dd)
終于到了基類QObject,這里就直接把QPushButtonPrivate的指針賦值給了d_ptr(還記得這個變量名稱吧)
最終在QPushButton構造時同時產生的new QPushButtonPrivate被寫到了QObject中的d_ptr中
- QObject::QObject(QObjectPrivate &dd, QObject *parent)
- : d_ptr(&dd)
- {
- Q_D(QObject);
- ::qt_addObject(d_ptr->q_ptr = this);
- QThread *currentThread = QThread::currentThread();
- d->thread = currentThread ? QThreadData::get(currentThread)->id : -1;
- Q_ASSERT_X(!parent || parent->d_func()->thread == d->thread, "QObject::QObject()",
- "Cannot create children for a parent that is in a different thread.");
- if (parent && parent->d_func()->thread != d->thread)
- parent = 0;
- if (d->isWidget) {
- if (parent) {
- d->parentparent = parent;
- d->parent->d_func()->children.append(this);
- }
- // no events sent here, this is done at the end of the QWidget constructor
- } else {
- setParent(parent);
- }
- }
然后執行QObject的構造函數,這里主要是一些線程的處理,先不理它
- QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WFlags f)
- : QObject(dd, ((parent && (parent->windowType() == Qt::Desktop)) ? 0 : parent)), QPaintDevice()
- {
- d_func()->init((parent && parent->windowType() == Qt::Desktop ? parent : 0), f);
- }
然后是QWidget的構造函數,這里調用了數據類QWidgetPrivate的init函數,這個函數不是虛函數,因此靜態解析成QWidgetPrivate的init函數調用
- QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
- : QWidget(dd, parent, 0)
- {
- Q_D(QAbstractButton);
- d->init();
- }
然后是QAbstractButton的構造函數,這里調用了數據類QAbstractButton的init函數,這個函數不是虛函數,因此靜態解析成QAbstractButton的init函數調用
- QPushButton::QPushButton(const QString &text, QWidget *parent)
- : QAbstractButton(*new QPushButtonPrivate, parent)
- {
- Q_D(QPushButton);
- d->init();
- setText(text);
- }
然后是QPushButton的構造函數,這里調用了數據類QPushButton的init函數,這個函數不是虛函數,因此靜態解析成QPushButton的init函數調用。
#p#
現在的事情很清楚了,總結一下:
QPushButton在構造的時候同時生成了QPushButtonPrivate指針,QPushButtonPrivate創建時依次調用數據類基類的構造函數
QPushButton的構造函數中顯示的調用了基類的構造函數并把QPushButtonPrivate指針傳遞過去,QPushButton創建時依次調用接口類基類的構造函數
在接口類的構造函數中調用了平行數據類的init函數,因為這個函數不是虛函數,因此就就是此次調用了數據類的init函數
需要指出的是,為什么QPushButtonPrivate實體類指針要轉換為引用呢?為什么不是直接傳遞指針?結論是人家喜歡這樣寫,就是不傳指針傳引用,而且要用一個*new之類的怪異語法,真叫人沒有辦法,其實這里用指針是一樣的,代碼看起來也自然一些.
- delete quit;
說完了構造,再說說析構
- QPushButton::~QPushButton()
- {
- }
這里當然會調用QPushButton的析構函數了
- QAbstractButton::~QAbstractButton()
- {
- #ifndef QT_NO_BUTTONGROUP
- Q_D(QAbstractButton);
- if (d->group)
- d->group->removeButton(this);
- #endif
- }
然后是QAbstractButton的析構函數
- QWidget::~QWidget()
- {
- Q_D(QWidget);
- ...
- }
然后是QWidget的析構函數,這里洋洋灑灑一大堆代碼,先不管它
- QObject::~QObject()
- {
- ...
- }
***是QObject的析構函數,這里也是洋洋灑灑的一大堆
- Q_D(QObject);
- if (d->wasDeleted) {
- #if defined(QT_DEBUG)
- qWarning("Double QObject deletion detected");
- #endif
- return;
- }
- d->wasDeleted = true;
這些沒有什么好說的,就是設一個wasDeleted的標志,防止再被引用,對于單線程情況下,馬上就要被刪除了,還搞什么標記啊,根本沒用,但是對于多線程情況下,這個標記應該是有用的
- // set all QPointers for this object to zero
- GuardHash *hash = ::guardHash();
- if (hash) {
- QWriteLocker locker(guardHashLock());
- GuardHash::iterator it = hash->find(this);
- const GuardHash::iterator end = hash->end();
- while (it.key() == this && it != end) {
- *it.value() = 0;
- it = hash->erase(it);
- }
- }
這里是支持QPointers的實現代碼,我們以后再說
- emit destroyed(this);
Qt的一個指針刪除時要發送destroyed信號,一般情況下是沒有槽來響應的
- QConnectionList *list = ::connectionList();
- if (list) {
- QWriteLocker locker(&list->lock);
- list->remove(this);
- }
這里清除了信號槽機制中的記錄
- if (d->pendTimer) {
- // have pending timers
- QThread *thr = thread();
- if (thr || d->thread == 0) {
- // don't unregister timers in the wrong thread
- QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance(thr);
- if (eventDispatcher)
- eventDispatcher->unregisterTimers(this);
- }
- }
這里清除定時器
- d->eventFilters.clear();
這里清除事件過濾機制
- // delete children objects
- if (!d->children.isEmpty()) {
- qDeleteAll(d->children);
- d->children.clear();
- }
這里清除所有子類指針,當然每個子類指針清除時又會清除它的所有子類,因此Qt中new出來的指針很少有顯示對應的delete,因為只要最上面的指針被框架刪除了,
它所連帶的所有子類都被自動刪除了
- {
- QWriteLocker locker(QObjectPrivate::readWriteLock());
- ::qt_removeObject(this);
- /*
- theoretically, we cannot check d->postedEvents without
- holding the postEventList.mutex for the object's thread,
- but since we hold the QObjectPrivate::readWriteLock(),
- nothing can go into QCoreApplication::postEvent(), which
- effectively means noone can post new events, which is what
- we are trying to prevent. this means we can safely check
- d->postedEvents, since we are fairly sure it will not
- change (it could, but only by decreasing, i.e. removing
- posted events from a differebnt thread)
- */
- if (d->postedEvents > 0)
- QCoreApplication::removePostedEvents(this);
- }
- if (d->parent) // remove it from parent object
- d->setParent_helper(0);
- delete d;
- d_ptr = 0;
這里要刪除相關的數據類指針了
小結:Qt 源碼分析 QObject的內容介紹完了,希望本文對你有所幫助!




















