Qt原始碼之d指標分析(QObject,QObjectPrivate)
前言
閱讀過Qt原始碼的同學一定對d指標不陌生,前段時間其實寫過一次關於Qt d指標的文章,但是感覺不夠透徹就刪除了,這次打算徹底地詳細地再分析一次。
Pimpl機制
對Pimpl機制不熟悉的先熟悉下Pimpl機制Pimpl機制。Qt的d指標其實主要還是採用了Pimpl機制。關於Pimpl機制的優點:
- 降低耦合
- 資訊隱藏
- 降低編譯依賴,提高編譯速度
- 介面與實現分離
其實Pimpl機制最大作用是實現二進位制相容,所謂“二進位制相容性”指的就是在升級(也可能是 bug fix)庫檔案的時候,不必重新編譯使用這個庫的可執行檔案或使用這個庫的其他庫檔案,程式的功能不被破壞。如果庫A升級沒有能夠做到二進位制相容,那麼所有依賴它的程式(或庫)都需要重新編譯,否則會出現各種未知異常,其直接現象就是程式莫名其妙地掛掉。想想Qt這麼大的庫,版本這麼多如果每次都要重新編譯那麼代價是很大的,當然Pimpl也是有缺點的,多了一個指標,記憶體肯定會增大。
原始碼分析
先以QObject類為例進行分析,以QObject為基類的所有qt的類都會呼叫Q_DECLARE_PRIVATE這個巨集
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
Q_DECLARE_PRIVATE(QObject)
...
}
來看下Q_DECLARE_PRIVATE這個巨集:
#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;
那麼Q_DECLARE_PRIVATE(QObject)展開就是:
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
friend class QObjectPrivate ;
也就是:
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
friend class QObjectPrivate;
...
}
qGetPtrHelper:
template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
d_func中d_ptr:
class Q_CORE_EXPORT QObject
{
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
}
QObjectData只是進行了簡單的封裝,它儲存了一些簡單的標誌位,當前類的例項、父類和子類們等等。
class Q_CORE_EXPORT QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint deleteLaterCalled : 1;
uint unused : 24;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
};
可以看到d_ptr是一個QObjectData的智慧指標,因為是protected許可權的,所以QObject的所有子類都能訪問它,d_func函式把它強制轉換成當前類的QObjectPrivate指標型別(QObjectPrivate繼承於QObjectData),並返回該指標,這樣我們在每個QObject的子類中使用該巨集就能完成轉換。那麼d_func這個函式在什麼時候呼叫呢?接著看qobject.cpp檔案的一個建構函式:
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
Q_D(QObject);
d_ptr->q_ptr = this;
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
...
d指標出現了,它是從哪裡來的呢,先看下Q_D這個巨集:
#define Q_D(Class) Class##Private * const d = d_func()
Q_D(QObject); 即:
QObjectPrivate * const d = d_func();
到這裡很明白了,d指標原來是通過巨集Q_D獲得的,也就是函式d_func的返回值,上文我們分析過了,d_func通過Q_DECLARE_PRIVATE巨集來獲得,將QObjectData指標轉換成當前類的QObjectPrivate指標型別,並返回該指標。
來看一個例子,QObject的setObjectName函式:
/*
Sets the object's name to \a name.
*/
void QObject::setObjectName(const QString &name)
{
Q_D(QObject);
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
if (d->extraData->objectName != name) {
d->extraData->objectName = name;
emit objectNameChanged(d->extraData->objectName, QPrivateSignal());
}
}
先簡單總結下,Qt採用了Pimpl機制,也就是私有實現,以QObject為基類的類的具體實現放在一個XXXPrivate中,標頭檔案中通過巨集Q_DECLARE_PRIVATE來返回將d_ptr轉換為當前類的XXXPrivate的一個指標,然後cpp中通過巨集Q_D來獲取這個d指標。到這裡你可能會有一個疑問了,QObject的子類的XXXPrivate的指標是如何傳遞給d_ptr的? 帶著這個問題我們往下看。
首先我們先看一個QPushButton的例子,將Widget設為QPushButton的parent:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
...
QPushButton *button = new QPushButton(this);
...
}
我們依次檢視建構函式看看發生了什麼:
QPushButton,呼叫了QAbstractButton的一個protected許可權的建構函式:
QPushButton::QPushButton(QWidget *parent)
: QAbstractButton(*new QPushButtonPrivate, parent)
{
Q_D(QPushButton);
d->init();
}
QAbstractButton,又呼叫了QWidget的一個proteced許可權的建構函式:
/*! \internal
*/
QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
: QWidget(dd, parent, 0)
{
Q_D(QAbstractButton);
d->init();
}
QWidget,又呼叫了QObject的一個proteced許可權的建構函式:
/*! \internal
*/
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
: QObject(dd, 0), QPaintDevice()
{
Q_D(QWidget);
QT_TRY {
d->init(parent, f);
} QT_CATCH(...) {
QWidgetExceptionCleaner::cleanup(this, d_func());
QT_RETHROW;
}
}
QObject 的proteced許可權的建構函式,可以看到QPushButton建構函式中的*new QPushButtonPrivate,最終傳遞給了d_ptr。
/*!
\internal
*/
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
...
}
一張圖方便理解:
拓展
qt中以QObject基類的類的建構函式都會指定一個parent的引數,那麼這個引數有什麼用呢?比如上文中QPushButton,指定了this也就是Widget類為parent,在一次次呼叫父類建構函式時會呼叫到QWidget的一個建構函式:
/*! \internal
*/
QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
: QObject(dd, 0), QPaintDevice()
{
Q_D(QWidget);
QT_TRY {
d->init(parent, f);
} QT_CATCH(...) {
QWidgetExceptionCleaner::cleanup(this, d_func());
QT_RETHROW;
}
}
引數中parent也就是new QPushButton(this) 時指定為parent的this指標。接著會呼叫 d->init(parent, f) 這個函式:
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
Q_Q(QWidget);
if (Q_UNLIKELY(!qobject_cast<QApplication *>(QCoreApplication::instance())))
qFatal("QWidget: Cannot create a QWidget without QApplication");
Q_ASSERT(allWidgets);
if (allWidgets)
allWidgets->insert(q);
...
}
又出現一個Q_Q巨集,經過類似的分析發現就是定義了一個q指標指向了d_ptr指標的q_ptr成員;allWidgets時QWidgetPrivate的一個靜態成員,儲存了所有的以QWidget為基類的指標。這個q指向的q_ptr在最終呼叫QObject建構函式時初始化的:
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_D(QObject);
d_ptr->q_ptr = this;
...
}
繼續看 void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) 這個函式:
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
...
else if (parentWidget)
q->setParent(parentWidget, data.window_flags);
...
}
呼叫的void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)這個函式:
void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
...
d->setParent_sys(parent, f);
...
}
呼叫的時void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) 這個函式:
void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
{
...
QObjectPrivate::setParent_helper(newparent);
...
}
呼叫的void QObjectPrivate::setParent_helper(QObject *o) 這個函式:
void QObjectPrivate::setParent_helper(QObject *o)
{
...
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData != parent->d_func()->threadData) {
qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");
parent = 0;
return;
}
parent->d_func()->children.append(q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent, &e);
}
}
}
...
}
現在很明瞭了,QPushButton設定parent最終傳遞給了d->parent;我們還可以看到parent->d_func()->children.append(q);這行程式碼其實是獲取到了parent的d指標,然後把當前的指標儲存到了parent的d指標的children的變數中。至此我們大概瞭解了d指標中的parent和children是如何賦值的。
建構函式差不多了,來看下QObject的解構函式:
QObject::~QObject()
{
...
if (!d->children.isEmpty())
d->deleteChildren();
...
}
...
void QObjectPrivate::deleteChildren()
{
Q_ASSERT_X(!isDeletingChildren, "QObjectPrivate::deleteChildren()", "isDeletingChildren already set, did this function recurse?");
isDeletingChildren = true;
// delete children objects
// don't use qDeleteAll as the destructor of the child might
// delete siblings
for (int i = 0; i < children.count(); ++i) {
currentChildBeingDeleted = children.at(i);
children[i] = 0;
delete currentChildBeingDeleted;
}
children.clear();
currentChildBeingDeleted = 0;
isDeletingChildren = false;
}
看到了嗎,如果一個以QObject為基類的類的例項delete的話它的所有parent都會被刪除掉,所以一個類指定了parent的話就我們就不必太過關心該類的記憶體釋放問題,這也是在Qt記憶體洩漏總結(包括檢測工具)這篇文章中提到的。