Qt QObject


【1】Qt的QObject

1.測試代碼如下:

 1 #include<QApplication>
 2 #include<QPushButton>
 3 #include<QDebug>
 4 using namespace std;
 5 
 6 int main(int argc, char *argv[])
 7 {
 8     QApplication app(argc, argv);
 9     int nSize = sizeof(QObject);
10     qDebug() << nSize << endl; // 8
11     QPushButton* pQuit = new QPushButton("Quit");
12     delete pQuit;
13     return app.exec();
14 }

QObject是Qt類體系的唯一基類,重要性就像MFC中的CObject或Delphi中的TObject,是Qt各種功能的活水源頭。此句代碼:

1     int nSize = sizeof(QObject);
2     qDebug() << nSize << endl; // 8

QObject的大小是8,除了虛函數表(即所謂的虛表)指針需要4個字節以外,另外的4個字節是指d_ptr(指針成員變量:QObjectData *d_ptr;)

備注:在最新版本中,被替換為QScopedPointer<QObjectData> d_ptr;

那么,QObjectData是個什么鬼?且往下看:

經分析,QObject類的數據成員被封裝在QObjectData類中了,為什么要如此封裝數據呢?

原因簡述:Qt中有一個很重要的設計模式,句柄(方法)—實體(數據)模式,也就是以QObject為基類的類一般都是句柄類,一般會有一個指針指向一個實體類(數據成員類),在實體類中保存全部的數據成員。而且,一般情況下這個指針還是受保護成員變量,方便其子類的使用。因此,也可以說,與句柄類繼承關系平行的也有一套實體類派生體系。所以,准確的說,Qt的基類其實有兩個:一個是QObject,這是句柄類的唯一基類;另一個是QObjectData,這是實體類的基類。

QObjectData類定義如下:

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

QObject *q_ptr; 這個指針指向此實體類對應的句柄類,此指針與上面的指針QScopedPointer<QObjectData> d_ptr; 遙相呼應,使得句柄類和實體類可以雙向的引用,為什么是這樣的命名方式呢?可能q指的是Qt接口類,d指的是Data實體類。當然這是猜測,但是或許可以方便你記憶。在Qt中,這兩個指針名字是非常重要的,必須記住(嗯哼?為什么重要呢?請看下面兩個宏的定義)。

但是,僅僅如此還是不容易使用這兩個指針,因為它們都是基類的類型,難道每次使用都要類型轉換嗎?為了簡單起見,Qt在這里聲明了兩個宏:

 1 template <typename T>
 2 static inline T *qGetPtrHelper(T *ptr) { return ptr; }
 3 
 4 #define Q_DECLARE_PRIVATE(Class) \
 5     inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
 6     inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
 7     friend class Class##Private;
 8 
 9 #define Q_DECLARE_PUBLIC(Class)                                    \
10     inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
11     inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
12     friend class Class;

只要在類的頭文件中使用這兩個宏,就可以通過函數直接得到實體類和句柄類的實際類型了,而且這里還聲明了友元,使得實體類和句柄類連訪問權限也不用顧忌了。而且為了實體類與句柄類cpp文件中調用的方便,更是直接聲明了以下兩個宏:

1 #define Q_D(Class) Class##Private * const d = d_func()
2 #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確實在內存中保存了所有類實例的樹型結構。

1     uint isWidget : 1;
2     uint blockSig : 1;
3     uint wasDeleted : 1;
4     uint isDeletingChildren : 1;
5     uint sendChildEvents : 1;
6     uint receiveChildEvents : 1;
7     uint isWindow : 1; //for QWindow
8     uint unused : 25;

這些代碼就簡單了,主要是一些標記位,為了節省內存開銷,這里采用了位域的語法,還保留了25位為unused,留做以后的擴充。

具體還是看一個例子吧!對這種句柄實體模式加深認識,這就是Qt中的按鈕類QPushButton,QPushButton的句柄類派生關系以及QPushButtonPrivate的實體類派生關系是:

可以看出,這里確實是一個平行體系,只不過實體類派生關系中多了一個QObjectPrivate,這個類封裝了線程處理,信號和槽機制等具體的實現,可以說它才是Qt實體類中真正起作用的基類,而QObjectData不過是一層淺淺的數據封裝而已。先不忙了解QObjectPrivate類中的接口和實現,我們先看看在Qt中,句柄類和實體類這兩條體系是如何構造的?

QPushButton* pQuit = new QPushButton("Quit"); 創建一個Qt的按鈕,簡簡單單一行代碼,其實背后大有玄機,請看如下代碼:

 1 QObject::QObject(QObjectPrivate &dd, QObject *parent)
 2     : d_ptr(&dd)
 3 {
 4     Q_D(QObject);
 5     d_ptr->q_ptr = this;
 6     d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
 7     d->threadData->ref();
 8     if (parent)
 9     {
10         QT_TRY
11         {
12             if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
13                 parent = 0;
14             if (d->isWidget)
15             {
16                 if (parent)
17                 {
18                     d->parent = parent;
19                     d->parent->d_func()->children.append(this);
20                 }
21                 // no events sent here, this is done at the end of the QWidget constructor
22             }
23             else
24             {
25                 setParent(parent);
26             }
27         }
28         QT_CATCH(...)
29         {
30             d->threadData->deref();
31             QT_RETHROW;
32         }
33     }
34     qt_addObject(this);
35 }
36 
37 QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
38     : QObject(dd, 0), QPaintDevice()
39 {
40     Q_D(QWidget);
41     QT_TRY
42     {
43         d->init(parent, f);
44     }
45     QT_CATCH(...)
46     {
47         QWidgetExceptionCleaner::cleanup(this, d_func());
48         QT_RETHROW;
49     }
50 }
51 
52 QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
53     : QWidget(dd, parent, 0)
54 {
55     Q_D(QAbstractButton);
56     d->init();
57 }
58 
59 QPushButton::QPushButton(const QString &text, QWidget *parent)
60     : QAbstractButton(*new QPushButtonPrivate, parent)
61 {
62     Q_D(QPushButton);
63     setText(text);
64     d->init();
65 }

首先QPushButton的構造函數中調用了QAbstractButton的構造函數,同時馬上new出來一個QPushButtonPrivate實體類,然后把對象傳遞給QAbstractButton

QAbstractButton的構造函數中再調用基類QWidget的構造函數,同時把QPushButtonPrivate實體類對象傳給基類

QWidget繼續做着同樣的事情

終於到了基類QObject,直接把QPushButtonPrivate的指針賦值給了d_ptr(還記得這個變量名稱吧)

最終在QPushButton構造時同時產生的new QPushButtonPrivate被寫到了QObject中的d_ptr中。

 1 QObject::QObject(QObjectPrivate &dd, QObject *parent)
 2     : d_ptr(&dd)
 3 {
 4     Q_D(QObject);
 5     d_ptr->q_ptr = this;
 6     d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
 7     d->threadData->ref();
 8     if (parent)
 9     {
10         QT_TRY
11         {
12             if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
13                 parent = 0;
14             if (d->isWidget)
15             {
16                 if (parent)
17                 {
18                     d->parent = parent;
19                     d->parent->d_func()->children.append(this);
20                 }
21                 // no events sent here, this is done at the end of the QWidget constructor
22             }
23             else
24             {
25                 setParent(parent);
26             }
27         }
28         QT_CATCH(...)
29         {
30             d->threadData->deref();
31             QT_RETHROW;
32         }
33     }
34     qt_addObject(this);
35 }

然后執行QObject的構造函數,這里主要是一些線程的處理,先不理它。

 1 QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
 2     : QObject(dd, 0), QPaintDevice()
 3 {
 4     Q_D(QWidget);
 5     QT_TRY
 6     {
 7         d->init(parent, f);
 8     }
 9     QT_CATCH(...)
10     {
11         QWidgetExceptionCleaner::cleanup(this, d_func());
12         QT_RETHROW;
13     }
14 }

然后是QWidget的構造函數,這里調用了數據類QWidgetPrivate的init函數,此函數不是虛函數,因此靜態解析成 QWidgetPrivate的init函數調用。

1 QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
2     : QWidget(dd, parent, 0)
3 {
4     Q_D(QAbstractButton);
5     d->init();
6 }

然后是QAbstractButton的構造函數,這里調用了數據類QAbstractButton的init函數,此函數不是虛函數,因此靜態解析成 QAbstractButtonPrivate的init函數調用。

1 QPushButton::QPushButton(const QString &text, QWidget *parent)
2     : QAbstractButton(*new QPushButtonPrivate, parent)
3 {
4     Q_D(QPushButton);
5     setText(text);
6     d->init();
7 }

然后是QPushButton的構造函數,這里調用了實體類QPushButtonPrivate的init函數,此函數不是虛函數,因此靜態解析成 QPushButtonPrivate的init函數調用。

現在的事情很清楚了。總結一下:

QPushButton在構造的時候同時生成了QPushButtonPrivate指針,QPushButtonPrivate創建時依次調用實體類基類的構造函數。QPushButton的構造函數中顯式的調用基類的構造函數並把QPushButtonPrivate指針傳遞過去,QPushButton創建時依次調用句柄類基類的構造函數,在句柄類的構造函數中調用了平行實體類的init函數,因為這個函數不是虛函數,因此就是每次調用了對應實體類的init函數。

delete pQuit;

說完了構造,再說說析構

  1 /*!
  2     Destroys the object, deleting all its child objects.
  3 
  4     All signals to and from the object are automatically disconnected, and
  5     any pending posted events for the object are removed from the event
  6     queue. However, it is often safer to use deleteLater() rather than
  7     deleting a QObject subclass directly.
  8 
  9     \warning All child objects are deleted. If any of these objects
 10     are on the stack or global, sooner or later your program will
 11     crash. We do not recommend holding pointers to child objects from
 12     outside the parent. If you still do, the destroyed() signal gives
 13     you an opportunity to detect when an object is destroyed.
 14 
 15     \warning Deleting a QObject while pending events are waiting to
 16     be delivered can cause a crash. You must not delete the QObject
 17     directly if it exists in a different thread than the one currently
 18     executing. Use deleteLater() instead, which will cause the event
 19     loop to delete the object after all pending events have been
 20     delivered to it.
 21 
 22     \sa deleteLater()
 23 */
 24 QObject::~QObject()
 25 {
 26     Q_D(QObject);
 27     d->wasDeleted = true;
 28     d->blockSig = 0; // unblock signals so we always emit destroyed()
 29 
 30     QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.load();
 31     if (sharedRefcount)
 32     {
 33         if (sharedRefcount->strongref.load() > 0)
 34         {
 35             qWarning("QObject: shared QObject was deleted directly. The program is malformed and may crash.");
 36             // but continue deleting, it's too late to stop anyway
 37         }
 38 
 39         // indicate to all QWeakPointers that this QObject has now been deleted
 40         sharedRefcount->strongref.store(0);
 41         if (!sharedRefcount->weakref.deref())
 42             delete sharedRefcount;
 43     }
 44 
 45     if (!d->isWidget && d->isSignalConnected(0))
 46     {
 47         QT_TRY
 48         {
 49             emit destroyed(this);
 50         }
 51         QT_CATCH(...)
 52         {
 53             // all the signal/slots connections are still in place - if we don't
 54             // quit now, we will crash pretty soon.
 55             qWarning("Detected an unexpected exception in ~QObject while emitting destroyed().");
 56             QT_RETHROW;
 57         }
 58     }
 59 
 60     if (d->declarativeData)
 61     {
 62         if (static_cast<QAbstractDeclarativeDataImpl*>(d->declarativeData)->ownedByQml1)
 63         {
 64             if (QAbstractDeclarativeData::destroyed_qml1)
 65                 QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this);
 66         }
 67         else
 68         {
 69             if (QAbstractDeclarativeData::destroyed)
 70                 QAbstractDeclarativeData::destroyed(d->declarativeData, this);
 71         }
 72     }
 73 
 74     // set ref to zero to indicate that this object has been deleted
 75     if (d->currentSender != 0)
 76         d->currentSender->ref = 0;
 77     d->currentSender = 0;
 78 
 79     if (d->connectionLists || d->senders)
 80     {
 81         QMutex *signalSlotMutex = signalSlotLock(this);
 82         QMutexLocker locker(signalSlotMutex);
 83 
 84         // disconnect all receivers
 85         if (d->connectionLists)
 86         {
 87             ++d->connectionLists->inUse;
 88             int connectionListsCount = d->connectionLists->count();
 89             for (int signal = -1; signal < connectionListsCount; ++signal)
 90             {
 91                 QObjectPrivate::ConnectionList &connectionList =
 92                     (*d->connectionLists)[signal];
 93 
 94                 while (QObjectPrivate::Connection *c = connectionList.first)
 95                 {
 96                     if (!c->receiver)
 97                     {
 98                         connectionList.first = c->nextConnectionList;
 99                         c->deref();
100                         continue;
101                     }
102 
103                     QMutex *m = signalSlotLock(c->receiver);
104                     bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
105 
106                     if (c->receiver)
107                     {
108                         *c->prev = c->next;
109                         if (c->next) c->next->prev = c->prev;
110                     }
111                     c->receiver = 0;
112                     if (needToUnlock)
113                         m->unlock();
114 
115                     connectionList.first = c->nextConnectionList;
116 
117                     // The destroy operation must happen outside the lock
118                     if (c->isSlotObject)
119                     {
120                         c->isSlotObject = false;
121                         locker.unlock();
122                         c->slotObj->destroyIfLastRef();
123                         locker.relock();
124                     }
125                     c->deref();
126                 }
127             }
128 
129             if (!--d->connectionLists->inUse)
130             {
131                 delete d->connectionLists;
132             }
133             else
134             {
135                 d->connectionLists->orphaned = true;
136             }
137             d->connectionLists = 0;
138         }
139 
140         /* Disconnect all senders:
141          * This loop basically just does
142          *     for (node = d->senders; node; node = node->next) { ... }
143          *
144          * We need to temporarily unlock the receiver mutex to destroy the functors or to lock the
145          * sender's mutex. And when the mutex is released, node->next might be destroyed by another
146          * thread. That's why we set node->prev to &node, that way, if node is destroyed, node will
147          * be updated.
148          */
149         QObjectPrivate::Connection *node = d->senders;
150         while (node)
151         {
152             QObject *sender = node->sender;
153             // Send disconnectNotify before removing the connection from sender's connection list.
154             // This ensures any eventual destructor of sender will block on getting receiver's lock
155             // and not finish until we release it.
156             sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
157             QMutex *m = signalSlotLock(sender);
158             node->prev = &node;
159             bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
160             //the node has maybe been removed while the mutex was unlocked in relock?
161             if (!node || node->sender != sender)
162             {
163                 // We hold the wrong mutex
164                 Q_ASSERT(needToUnlock);
165                 m->unlock();
166                 continue;
167             }
168             node->receiver = 0;
169             QObjectConnectionListVector *senderLists = sender->d_func()->connectionLists;
170             if (senderLists)
171                 senderLists->dirty = true;
172 
173             QtPrivate::QSlotObjectBase *slotObj = Q_NULLPTR;
174             if (node->isSlotObject)
175             {
176                 slotObj = node->slotObj;
177                 node->isSlotObject = false;
178             }
179 
180             node = node->next;
181             if (needToUnlock)
182                 m->unlock();
183 
184             if (slotObj)
185             {
186                 if (node)
187                     node->prev = &node;
188                 locker.unlock();
189                 slotObj->destroyIfLastRef();
190                 locker.relock();
191             }
192         }
193     }
194 
195     if (!d->children.isEmpty())
196         d->deleteChildren();
197 
198     qt_removeObject(this);
199 
200     if (d->parent)        // remove it from parent object
201         d->setParent_helper(0);
202 }
203 
204 /*!
205     Destroys the widget.
206 
207     All this widget's children are deleted first. The application
208     exits if this widget is the main widget.
209 */
210 QWidget::~QWidget()
211 {
212     Q_D(QWidget);
213     d->data.in_destructor = true;
214 
215 #if defined (QT_CHECK_STATE)
216     if (paintingActive())
217         qWarning("QWidget: %s (%s) deleted while being painted", className(), name());
218 #endif
219 
220 #ifndef QT_NO_GESTURES
221     foreach (Qt::GestureType type, d->gestureContext.keys())
222         ungrabGesture(type);
223 #endif
224 
225     // force acceptDrops false before winId is destroyed.
226     d->registerDropSite(false);
227 
228 #ifndef QT_NO_ACTION
229     // remove all actions from this widget
230     for (int i = 0; i < d->actions.size(); ++i)
231     {
232         QActionPrivate *apriv = d->actions.at(i)->d_func();
233         apriv->widgets.removeAll(this);
234     }
235     d->actions.clear();
236 #endif
237 
238 #ifndef QT_NO_SHORTCUT
239     // Remove all shortcuts grabbed by this
240     // widget, unless application is closing
241     if (!QApplicationPrivate::is_app_closing && testAttribute(Qt::WA_GrabbedShortcut))
242         qApp->d_func()->shortcutMap.removeShortcut(0, this, QKeySequence());
243 #endif
244 
245     // delete layout while we still are a valid widget
246     delete d->layout;
247     d->layout = 0;
248     // Remove myself from focus list
249 
250     Q_ASSERT(d->focus_next->d_func()->focus_prev == this);
251     Q_ASSERT(d->focus_prev->d_func()->focus_next == this);
252 
253     if (d->focus_next != this)
254     {
255         d->focus_next->d_func()->focus_prev = d->focus_prev;
256         d->focus_prev->d_func()->focus_next = d->focus_next;
257         d->focus_next = d->focus_prev = 0;
258     }
259 
260     QT_TRY
261     {
262 #ifndef QT_NO_GRAPHICSVIEW
263         const QWidget* w = this;
264         while (w->d_func()->extra && w->d_func()->extra->focus_proxy)
265             w = w->d_func()->extra->focus_proxy;
266         QWidget *window = w->window();
267         QWExtra *e = window ? window->d_func()->extra : 0;
268         if (!e || !e->proxyWidget || (w->parentWidget() && w->parentWidget()->d_func()->focus_child == this))
269 #endif
270         clearFocus();
271     }
272     QT_CATCH(...)
273     {
274         // swallow this problem because we are in a destructor
275     }
276 
277     d->setDirtyOpaqueRegion();
278 
279     if (isWindow() && isVisible() && internalWinId())
280     {
281         QT_TRY
282         {
283             d->close_helper(QWidgetPrivate::CloseNoEvent);
284         }
285         QT_CATCH(...)
286         {
287             // if we're out of memory, at least hide the window.
288             QT_TRY
289             {
290                 hide();
291             }
292             QT_CATCH(...)
293             {
294                 // and if that also doesn't work, then give up
295             }
296         }
297     }
298 
299 #if defined(Q_WS_WIN) || defined(Q_WS_X11)|| defined(Q_WS_MAC)
300     else if (!internalWinId() && isVisible())
301     {
302         qApp->d_func()->sendSyntheticEnterLeave(this);
303     }
304 #endif
305     else if (isVisible())
306     {
307         qApp->d_func()->sendSyntheticEnterLeave(this);
308     }
309 
310     if (QWidgetBackingStore *bs = d->maybeBackingStore())
311     {
312         bs->removeDirtyWidget(this);
313         if (testAttribute(Qt::WA_StaticContents))
314             bs->removeStaticWidget(this);
315     }
316 
317     delete d->needsFlush;
318     d->needsFlush = 0;
319 
320     // The next 20 lines are duplicated from QObject, but required here
321     // since QWidget deletes is children itself
322     bool blocked = d->blockSig;
323     d->blockSig = 0; // unblock signals so we always emit destroyed()
324 
325     if (d->isSignalConnected(0))
326     {
327         QT_TRY
328         {
329             emit destroyed(this);
330         }
331         QT_CATCH(...)
332         {
333             // all the signal/slots connections are still in place - if we don't
334             // quit now, we will crash pretty soon.
335             qWarning("Detected an unexpected exception in ~QWidget while emitting destroyed().");
336             QT_RETHROW;
337         }
338     }
339 
340     if (d->declarativeData)
341     {
342         if (QAbstractDeclarativeData::destroyed)
343             QAbstractDeclarativeData::destroyed(d->declarativeData, this);
344         if (QAbstractDeclarativeData::destroyed_qml1)
345             QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this);
346         d->declarativeData = 0;                 // don't activate again in ~QObject
347     }
348 
349     d->blockSig = blocked;
350 
351 #ifdef Q_WS_MAC
352     // QCocoaView holds a pointer back to this widget. Clear it now
353     // to make sure it's not followed later on. The lifetime of the
354     // QCocoaView might exceed the lifetime of this widget in cases
355     // where Cocoa itself holds references to it.
356     extern void qt_mac_clearCocoaViewQWidgetPointers(QWidget *);
357     qt_mac_clearCocoaViewQWidgetPointers(this);
358 #endif
359 
360     if (!d->children.isEmpty())
361         d->deleteChildren();
362 
363     QApplication::removePostedEvents(this);
364 
365     QT_TRY
366     {
367         destroy();                                        // platform-dependent cleanup
368     }
369     QT_CATCH(...)
370     {
371         // if this fails we can't do anything about it but at least we are not allowed to throw.
372     }
373     --QWidgetPrivate::instanceCounter;
374 
375     if (QWidgetPrivate::allWidgets) // might have been deleted by ~QApplication
376         QWidgetPrivate::allWidgets->remove(this);
377 
378     QT_TRY
379     {
380         QEvent e(QEvent::Destroy);
381         QCoreApplication::sendEvent(this, &e);
382     }
383     QT_CATCH(const std::exception&)
384     {
385         // if this fails we can't do anything about it but at least we are not allowed to throw.
386     }
387 }
388 
389 /*!
390     Destroys the button.
391  */
392  QAbstractButton::~QAbstractButton()
393 {
394 #ifndef QT_NO_BUTTONGROUP
395     Q_D(QAbstractButton);
396     if (d->group)
397         d->group->removeButton(this);
398 #endif
399 }
400 
401 /*!
402     Destroys the push button.
403 */
404 QPushButton::~QPushButton()
405 {
406 }

這里當然會調用QPushButton的析構函數

然后,QAbstractButton的析構函數

然后,QWidget的析構函數,這里洋洋灑灑一大堆代碼,先不管它

最后,QObject的析構函數,這里也是洋洋灑灑的一大堆,主要做了一下幾件事:

(1)設一個wasDeleted的標志,防止再被引用,對於單線程情況下,馬上就要被刪除了,還搞什么標記啊,根本沒用,但是對於多線程情況下,這個標記應該是有用的

(2)Qt的一個指針刪除時要發送destroyed信號,一般情況下是沒有槽來響應的

(3)清除了信號槽機制中的記錄

(4)清除定時器

(5)清除事件過濾機制

(6)清除所有子類指針,當然每個子類指針清除時又會清除它的所有子類,因此Qt中new出來的指針很少有顯式對應的delete,因為只要最上面的指針被框架刪除了,它所連帶的所有子類都被自動刪除了

(7)刪除相關的數據類指針

【2】對象數據存儲

前言,為什么先說這個?

我們知道,在C++中,幾乎每一個類(class)中都需要有類的一些成員變量(class member variable),在通常情況下的做法如下:

1 class Person
2 {
3 private:
4     QString m_szName; // 姓名
5     bool m_bSex;      // 性別
6     int m_nAge;       // 年齡
7 };

就是在類定義的時候,直接把類成員變量定義在內面,甚至於把這些成員變量的存取方法直接定義成是public的,您是不是也這樣做過呢?

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

為了更容易理解QT是如何定義類成員變量的,我們先說一下QT 2.x 版本中的類成員變量定義方法,因為在 2.x 中的方法非常容易理解。然后再介紹 QT 4.4 中的類成員變量定義方法。

2.1 QT 2.x 中的方法

在定義class的時候(在.h文件中),只包含有一個類成員變量,只是定義一個成員數據指針,然后由這個指針指向一個數據成員對象,這個數據成員對象包含所有這個class的成員數據,然后在class的實現文件(.cpp文件)中,定義這個私有數據成員對象。示例代碼如下:

 1 // File name:  person.h
 2 struct PersonalDataPrivate; // 聲明私有數據成員類型
 3 class Person
 4 {
 5 public:
 6     Person ();   // constructor
 7     virtual ~Person ();  // destructor
 8     void setAge(const int);
 9     int getAge();
10 
11 private:
12     PersonalDataPrivate* d;
13 };
14 
15 // File name:  person.cpp
16 struct PersonalDataPrivate  // 定義私有數據成員類型
17 {
18     string m_szName; // 姓名
19     bool m_bSex;     // 性別
20     int m_nAge;      // 年齡
21 };
22 
23 // constructor
24 Person::Person ()
25 {
26     d = new PersonalDataPrivate;
27 }
28 
29 // destructor
30 Person::~Person ()
31 {
32     delete d;
33 }
34 
35 void Person::setAge(const int age)
36 {
37     if (age != d->m_nAge)
38     {
39         d->m_nAge = age;
40     }
41 }
42 
43 int Person::getAge()
44 {
45     return d->m_nAge;
46 }

在最初學習QT的時候,我也覺得這種方法很麻煩,但是隨着使用的增多,我開始很喜歡這個方法了。而且,現在寫的代碼,基本上都會用這種方法。具體說來,它有如下優點:

  • 減少頭文件的依賴性。把具體的數據成員都放到cpp文件中去,這樣,在需要修改數據成員的時候,只需要改cpp文件而不需要頭文件,這樣就可以避免一次因為頭文件的修改而導致所有包含了這個文件的文件全部重新編譯一次,尤其是當這個頭文件是非常底層的頭文件和項目非常龐大的時候,優勢明顯。同時,也減少了這個頭文件對其它頭文件的依賴性。可以把只在數據成員中需要用到的在cpp文件中include一次就可以,在頭文件中就可以盡可能的減少include語句。

  • 增強類的封裝性。這種方法增強了類的封裝性,無法再直接存取類成員變量,而必須寫相應的 get/set 成員函數來做這些事情。 關於這個問題,仁者見仁,智者見智,每個人都有不同的觀點。有些人就是 喜歡把類成員變量都定義成public的,在使用的時候方便。只是我個人不喜歡這種方法,當項目變得很大的時候,有非常多的人一起在做這個項目的時候,自己所寫的代碼處於底層有非常多的人需要使用(#include)的時候,這個方法的弊端就充分的體現出來了。

還有,我不喜歡 QT 2.x 中把數據成員的變量名都定義成只有一個字母d,看起來很不直觀,尤其是在search的時候,很不方便。但是,QT kernel 中的確就是這么干的。

那么,在最新的 QT4 里面是如何實現的呢?請關注下一節。

2.2 QT 4.4.x 中的方法

在QT4.4中,類成員變量定義方法的出發點沒有變化,只是在具體的實現手段上發生了非常大的變化,下面具體來看。

在QT4.4中,使用了非常多的宏來做事,這無疑增加了理解 QT source code的難度,不知道他們是不是從MFC學來的。就連在定義類成員數據變量這件事情上,也大量的使用了宏。

在這個版本中,類成員變量不再是給每一個class都定義一個私有的成員,而是把這一項common的工作放到了最基礎的基類 QObject 中,然后定義了一些相關的方法來存取。

 1 // file name: qobject.h
 2 class QObjectData
 3 {
 4 public:
 5     virtual ~QObjectData() = 0;
 6     // 省略
 7 };
 8 
 9 class QObject
10 {
11     Q_DECLARE_PRIVATE(QObject)
12 public:
13     QObject(QObject *parent = 0);
14 
15 protected:
16     QObject(QObjectPrivate &dd,QObject *parent = 0);
17 
18 protected:
19     QObjectData *d_ptr;
20 }

這些代碼就是在 qobject.h 這個頭文件中的。

從句柄類QObject的定義中,可以看到,數據成員的定義為:QObjectData *d_ptr; 

之所以定義成 protected 類型,就是要讓所有的派生類都可以存取這個變量,而在外部卻不可以直接存取這個變量。

而QObjectData的定義卻放在了這個頭文件中,其目的就是為了要所有從QObject繼承出來的類的成員變量也都相應的要在QObjectData這個class繼承出來。

而純虛的析構函數又決定了兩件事:

  • 這個class不能直接被實例化。換句話說就是,如果你寫了這么一行代碼:new QObjectData,這行代碼一定會出錯,compile的時候是無法通過的。
  • 當delete這個指針變量的時候,這個指針變量是指向的任意從 QObjectData 繼承出來的對象的時候,這個對象都能被正確delete,而不會產生錯誤(諸如,內存泄漏之類的)。

我們再來看看這個宏做了什么,Q_DECLARE_PRIVATE(QObject)

1 #define Q_DECLARE_PRIVATE(Class) \
2     inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
3     inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
4     friend class Class##Private;

這個宏主要是定義了兩個重載的函數d_func()。作用就是把在QObject這個class中定義的數據成員變量d_ptr安全的轉換成為每一個具體的class的數據成員類型(實體類)指針。

來,我們看一下在QObject這個class中,這個宏展開之后的情況,就一幕了然了。

將 Q_DECLARE_PRIVATE(QObject) 展開后,就是下面的代碼:

 1 inline QObjectPrivate* d_func()
 2 {
 3     return reinterpret_cast<QObjectPrivate *>(d_ptr);
 4 }
 5 
 6 inline const QObjectPrivate* d_func() const
 7 {
 8     return reinterpret_cast<const QObjectPrivate *>(d_ptr);
 9 }
10 
11 friend class QObjectPrivate;

宏展開之后,新的問題又來了,這個 QObjectPrivate 是從哪里來的?在QObject這個類中,為什么不直接使用 QObjectPrivate 來作為數據成員變量的類型呢?還記得我們剛才說過嗎,QObjectData這個類的析構函數是純虛函數,也就意味着這個類是不能實例化的。所以,QObject這個類的數據成員變量的實際類型是從QObjectData繼承出來的,它就是QObjectPrivate。

這個類中保存了許多非常重要而且有趣的東西,其中包括 QT 最核心的 signal 和 slot 的數據、屬性數據等等,我們將會在后面詳細講解,現在我們來看一下它的定義:

下面就是這個類的定義:

1 class QObjectPrivate : public QObjectData
2 {
3     Q_DECLARE_PUBLIC(QObject)
4 public:
5     QObjectPrivate(int version = QObjectPrivateVersion);
6     virtual ~QObjectPrivate();
7     // 省略
8 };

那么,這個 QObjectPrivate 和 QObject 是什么關系呢?他們是如何關聯在一起的呢?

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

 1 // file name: qobject.cpp
 2 QObject::QObject(QObject *parent)
 3 
 4     : d_ptr(new QObjectPrivate)
 5 
 6 {
 7     // ………………………
 8 }
 9 
10 QObject::QObject(QObjectPrivate &dd, QObject *parent)
11     : d_ptr(&dd)
12 {
13     // …………………
14 }

怎么樣,是不是 一目了然呀?

從第一個構造函數可以很清楚的看出來,QObject類中的 d_ptr 指針將指向一個 QObjectPrivate 的對象,而QObjectPrivate這個類是從QObjectData繼承出來的。

這第二個構造函數干什么用的呢?從 QObject類的定義中,我們可以看到,第二個構造函數是被定義為 protected 類型的,即說明,這個構造函數只能被繼承的類(子類)使用,(應用端)不能使用這個構造函數來直接構造一個QObject對象。也就是說,如果你寫一條下面的語句,編譯的時候是會失敗的: 

1 new QObject(*new QObjectPrivate, NULL)

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

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

 1 QObject::QObject(QObjectPrivate &dd, QObject *parent)
 2     : d_ptr(&dd)
 3 {
 4     // …………………
 5 }
 6 
 7 class QWidget : public QObject, public QPaintDevice 
 8 {
 9     Q_OBJECT
10     Q_DECLARE_PRIVATE(QWidget)
11     // ..................... 
12 }

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

1 QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
2     : QObject(*new QWidgetPrivate, 0)
3     , QPaintDevice()
4 {
5     d_func()->init(parent, f); 
6 }

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

1 class QWidgetPrivate : public QObjectPrivate
2 {
3     Q_DECLARE_PUBLIC(QWidget)
4     // ..................... 
5 };

好了,這就把所有的事情都串聯起來了。

關於QWidget構造函數中的唯一的語句 d_func()->init(parent, f) 我們注意到在類的定義中有這么一句話: Q_DECLARE_PRIVATE(QWidget) 

我們前面提到這個宏,當把這個宏展開之后,就是這樣的:

1 #define Q_DECLARE_PRIVATE(Class) \
2     inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
3     inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
4     friend class Class##Private;

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

小 結:

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

【3】Qt對象之間的父子關系

很多C/C++初學者常犯的一個錯誤就是,使用malloc、new分配了一塊內存卻忘記釋放,導致內存泄漏。

Qt的對象模型提供了一種Qt對象之間的父子關系,當很多個對象都按一定次序建立起來這種父子關系的時候,就組織成了一顆樹。當delete一個父對象的時候,Qt的對象模型機制保證了會自動的把它的所有子對象,以及孫對象等等全部delete,從而保證不會有內存泄漏的情況發生。

任何事情都有正反兩面作用,這種機制看上去挺好,但是卻會對很多Qt的初學者造成困擾,我經常給別人回答的問題是:

1:new了一個Qt對象之后,在什么情況下應該delete它?

2:Qt的析構函數是不是有bug?

3:為什么正常delete一個Qt對象卻會產生segment fault?

等等諸如此類的問題,這篇文章就是針對這個問題的詳細解釋。 在每一個Qt對象中,都有一個鏈表,這個鏈表保存有它所有子對象的指針。當創建一個新的Qt對象的時候,如果把另外一個Qt對象指定為這個對象的父對象, 那么父對象就會在它的子對象鏈表中加入這個子對象的指針。另外,對於任意一個Qt對象而言,在其生命周期的任何時候,都還可以通過setParent函數重新設置它的父對象。當一個父對象在被delete的時候,它會自動的把它所有的子對象全部delete。當一個子對象在delete的時候,會把它自己從它的父對象的子對象鏈表中刪除。

QWidget是所有在屏幕上顯示出來的界面對象的基類,它擴展了Qt對象的父子關系。一個Widget對象也就自然的成為其父Widget對象的子Widget,並且顯示在它的父Widget的坐標系統中。例如,一個對話框(QDialog)上的按鈕(QButton)應該是這個對話框的子Widget。 關於Qt對象的new和delete,下面我們舉例說明:

例如,下面這一段代碼是正確的:

1 int main()
2 {
3     QObject* pObjParent = new QObject(NULL);
4     QObject* pObjChild1 = new QObject(pObjParent);
5     QObject* pObjChild2 = new QObject(pObjParent);
6     delete pObjParent;
7 }

如果我們把上面這段代碼改成這樣,也是正確的:

1 int main()
2 {
3     QObject* pObjParent = new QObject(NULL);
4     QObject* pObjChild1 = new QObject(pObjParent);
5     QObject* pObjChild2 = new QObject(pObjParent);
6     delete pObjChild1;
7     delete pObjParent;
8 }

在這段代碼中,我們就對比一下和上一段代碼不一樣的地方,就是在delete pObjParent對象之前,先delete pObjChild1對象。在delete pObjChild1對象的時候,pObjChild1對象會自動的把自己從pObjParent對象的子對象鏈表中刪除,也就是說,在pObjChild1對象被delete完成之后,pObjParent對象就只有一個子對象(pObjChild2)了。然后在delete pObjParent對象的時候,會自動把pObjChild2對象也delete。所以,這段代碼也是安全的。(備注:Qt的這種設計對某些調試工具來說卻是不友好的,比如valgrind。比如上面這段代碼,valgrind工具在分析代碼的時候,就會認為 objChild2對象沒有被正確的delete,從而會報告說,這段代碼存在內存泄漏。哈哈,我們知道,這個報告是不對的。)我們再看一看這一段代碼:

1 int main()
2 {
3     QWidget window;
4     QPushButton quit("Exit", &window);
5 }

在這段代碼中,我們創建了兩個widget對象:第一個是window;第二個是quit。他們都是Qt對象,因為QPushButton是從 QWidget派生出來的,而QWidget是從QObject派生出來的。這兩個對象之間的關系:window對象是quit對象的父對象,由於他們都會被分配在棧(stack)上面,那么quit對象是不是會被析構兩次呢?我們知道,在一個函數體內部聲明的變量,在這個函數退出的時候就會被析構,那么在這段代碼中,window和quit兩個對象在函數退出的時候析構函數都會被調用。那么假設,如果是window的析構函數先被調用的話,它就會去delete quit對象;然后quit的析構函數再次被調用,程序就出錯了。事實情況不是這樣的,C++標准規定,本地對象的析構函數的調用順序與他們的構造順序相反。那么在這段代碼中,這就是quit對象的析構函數一定會比window對象的析構函數先被調用,所以,在window對象析構的時候,quit對象已經不存在了,不會被析構兩次。 如果我們把代碼改成這個樣子,就會出錯了,對照前面的解釋,請你自己來分析一下吧。

1 int main() 
2 { 
3     QPushButton quit("Exit"); 
4     QWidget window; 
5     quit.setParent(&window); 
6 }

構建無誤,運行崩潰,提示如下:

我們自己在寫程序的時候,也必須重點注意一項,千萬不要delete子對象兩次,就像前面這段代碼那樣,程序肯定就crash了。

 

Good Good Study, Day Day Up.

順序 選擇 循環 總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM