1. 程式人生 > >Qt原始碼之d指標分析(QObject,QObjectPrivate)

Qt原始碼之d指標分析(QObject,QObjectPrivate)

前言

  閱讀過Qt原始碼的同學一定對d指標不陌生,前段時間其實寫過一次關於Qt d指標的文章,但是感覺不夠透徹就刪除了,這次打算徹底地詳細地再分析一次。

Pimpl機制

  對Pimpl機制不熟悉的先熟悉下Pimpl機制Pimpl機制。Qt的d指標其實主要還是採用了Pimpl機制。關於Pimpl機制的優點:

  1. 降低耦合
  2. 資訊隱藏
  3. 降低編譯依賴,提高編譯速度
  4. 介面與實現分離

  其實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記憶體洩漏總結(包括檢測工具)這篇文章中提到的。