1. 程式人生 > >QT---之Q_D和d指標概念2

QT---之Q_D和d指標概念2

Qt為了使其動態庫最大程度上實現二進位制相容,引入了d指標的概念。那麼為什麼d指標能實現二進位制相容呢?為了回答這個問題,首先弄清楚什麼是二進位制相容?所謂二進位制相容動態庫,指的是一個在老版本庫下執行的程式,在不經過編譯的情況下,仍然能夠在新的版本庫下執行;需要經過編譯才能在新版本下執行,而不需要修改該程式原始碼,我們就說該動態庫是原始碼相容的。要使一個dll能達到二進位制相容,對於一個結構,對於一個物件,其資料模型應該不變,若有變動,比如在類中增加資料成員或刪除資料成員,其結果肯定影響物件的資料模型,從而導致原有資料程員在物件資料模型裡的位移發生變化,這樣的話編譯後的新版本庫很可能使程式發生崩潰,為了使在增加和新增項後不使物件資料模型大小發生變化,一種做法是預先分配若干個保留空間,當要新增項時,使用保留項。如下:

class A {


private:

int a;

int reserved[3];

};


class B {

private:

int a;

quint32 b : 1;

quint32 reserved : 31;

};

這樣的話,當樣增加項的時候,只需要利用reserved空間,這樣的話,物件模型就不會改變。但是這種做法很呆板,因為你不知道未來到底會有多少擴充套件項,少了不滿足要求,多了浪費空間。那麼有沒有一種更靈活的方法呢?如下:

class Data {

public:

int a;

};


class A {


private:

Data *d_ptr;

};

將A中的成員a放入Data 中,A中放入Data的一個指標,這樣的話,無論你向Data中新增多少資料,A的物件模型始終是4個位元組的大小(d_ptr指標的大小),這種做法是不是比上面的做法更靈活呢?d_ptr就是我們今天所要說的d指標,Qt為了實現二進位制相容,絕大數類中都包含有這樣的指標,下面我們一起來看看Qt的d指標是怎麼實現的:

如上圖,這個是Qt根結點的指標的一般形式,下面來看看非根結點的一般形式,

 

注意這裡QWidge派生自QObject,它裡面沒有d_ptr,但是它的成員函式可以訪問d_ptr,因為 d_ptr是保護成員,且它的物件模型包含 d_ptr(這是因為派生類繼承父類的所有成員)。

 

下面我們來看看Qt對上述兩種情況是怎麼實現的:

qobject.h檔案:

 
QObjectData {

public:

QObject *q_ptr;

...

};


class Q_CORE_EXPORT QObject

{

...

Q_DECLARE_PRIVATE(QObject)

public:

Q_INVOKABLE explicit QObject(QObject *parent=0);

virtual ~QObject();

...


protected:

QObject(QObjectPrivate &dd, QObject *parent = 0);

...


protected:

QScopedPointer<QObjectData> d_ptr;

...

};

如上,在這裡我算去了其他的項,只保留了於d_ptr有關的項,首先來看看Q_DECLARE_PRIVATE(QObject)是什麼:

#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;

再來看看qGetPtrHelper的定義:

template <typename T> static inline T *qGetPtrHelper(T *ptr)

{

return ptr;

}

再來看QScopePointer,它類似於智慧指標,這樣不用關心 d_ptr的釋放,當離開QScopePointer的作用範圍,QScopePointer會自動釋放d_ptr指向的堆記憶體,那麼這個指標是什麼時候生成的呢?q_ptr又是什麼時候賦值的呢?讓我們來看看qobject.cpp的實現:

QObject::QObject(QObject *parent)

: d_ptr(new QObjectPrivate)

{

Q_D(QObject);

d_ptr->q_ptr = this;

...

}


QObject::QObject(QObjectPrivate &dd, QObject *parent)

: d_ptr(&dd)

{

Q_D(QObject);

d_ptr->q_ptr = this;

...

}

我們看第一個建構函式,對於根結點的d_ptr指向new QObjectPrivate,而QObjectPrivate派生自QObjectData,那麼Q_D(QObject)巨集表示什麼意思呢?

 

#define Q_D(Class) Class##Private * const d = d_func()  

Q_D(QObject);翻譯如下:

QObjectPrivate * const d = d_func();  

不難看出Q_D(QObject);定義了一個QObjectPrivate的常量指標,指向d_func() 的返回值,而該返回值,正是d_ptr(見標頭檔案 d_func()的定義),因此同過Q_D巨集我們就可以訪問d指標了。

 

對於第二個建構函式稍後介紹,下面來看看非根結點的d_ptr的實現情況:

標頭檔案:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData

{

Q_DECLARE_PUBLIC(QObject)

...

};


class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate

{

Q_DECLARE_PUBLIC(QWidget)

...

};


class Q_GUI_EXPORT QWidget : public QObject

{

...

Q_DECLARE_PRIVATE(QWidget)

...

public:

...

explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);

...

};

​​​​​​​我們首先來看看Q_DECLARE_PUBLIC巨集:

#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;

​​​​​​​根據巨集定義,Q_DECLARE_PUBLIC(QObject)翻譯如下:

inline QObject *q_func()

{

return static_cast<QObject *>(q_ptr);

}

inline const QObject *q_func() const

{

return static_cast<const QObject *>(q_ptr);

}

friend class QObject;

Q_DECLARE_PUBLIC(QWidget)翻譯如下:

inline QWidget *q_func()

{

return static_cast<QWidget *>(q_ptr);

}

inline const QWidget *q_func() const

{

return static_cast<const QWidget *>(q_ptr);

}

friend class QWidget;

注意這裡的q_ptr是在QObjectData裡公有宣告的,QObjectPrivate,QWidgetPrivate都派生或間接派生自QObjectData,所以可以訪問q_ptr。

 

接下來看Q_DECLARE_PRIVATE(QWidget)的翻譯:

inline QWidgetPrivate *d_func()

{

return reinterpret_cast<QWidgetPrivate *>(qGetPtrHelper(d_ptr));

}

inline const QWidgetPrivate *d_func() const

{

return reinterpret_cast<const QWidgetPrivate *>(qGetPtrHelper(d_ptr));

}

friend class QWidgetPrivate;

接下來看看QWidget的建構函式的實現:

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)

: QObject(*new QWidgetPrivate, 0)

{

...

}

看到QObject(*new QwidgetPrivate, 0)這裡呼叫了QObject的第二個建構函式,將d_ptr指向new QWidgetPrivate所指向的堆記憶體。