[轉]QT中的D指針與Q指針

Qt爲了使其動態庫最大程度上實現二進制兼容,引入了d指針的概念。數據結構

那麼爲何d指針能實現二進制兼容呢?函數

爲了回答這個問題,首先弄清楚什麼是二進制兼容?ui

所謂二進制兼容動態庫,指的是一個在老版本庫下運行的程序,在不通過編譯的狀況下,仍然可以在新的版本庫下運行;須要通過編譯才能在新版本下運行,而不須要修改該程序源代碼,咱們就說該動態庫是源代碼兼容的。要使一個dll能達到二進制兼容,對於一個結構,對於一個對象,其數據模型應該不變,如有變更,好比在類中增長數據成員或刪除數據成員,其結果確定影響對象的數據模型,從而致使原有數據程員在對象數據模型裏的位移發生變化,這樣的話編譯後的新版本庫極可能使程序發生崩潰,爲了使在增長和添加項後不使對象數據模型大小發生變化,一種作法是預先分配若干個保留空間,當要添加項時,使用保留項。以下:this

class A {
private:
  int a;
  int reserved[3];
};spa

class B {
private:
  int a;
  quint32 b : 1;
  quint32 reserved : 31;
};翻譯

這樣的話,當增長項的時候,只須要利用reserved空間(組態王中好多數據結構就有保留字段,如變量的域),這樣的話,對象模型就不會改變。可是這種作法很呆板,由於你不知道將來到底會有多少擴展項,少了不知足要求,多了浪費空間。那麼有沒有一種更靈活的方法呢?以下:指針

class Data {
public:
  int a;
};對象

class A {
private:
  Data *d_ptr;
};blog

將A中的成員a放入Data 中,A中放入Data的一個指針,這樣的話,不管你向Data中添加多少數據,A的對象模型始終是4個字節的大小(d_ptr指針的大小),這種作法是否是比上面的作法更靈活呢?d_ptr就是咱們今天所要說的d指針,Qt爲了實現二進制兼容,絕大數類中都包含有這樣的指針,下面咱們一塊兒來看看Qt的d指針是怎麼實現的:繼承

如上圖,這個是Qt根結點的指針的通常形式,下面來看看非根結點的通常形式,

注意這裏QWidget派生自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)翻譯以下:

template <typename T> static inline T *qGetPtrHelper(T *ptr)
{
  return ptr;
}
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所指向的堆內存。

 還能夠參考https://wiki.qt.io/D-Pointer/zh,這個講的不錯!

相關文章
相關標籤/搜索