1. 程式人生 > >d指針在Qt上的應用及實現(有圖,很清楚)

d指針在Qt上的應用及實現(有圖,很清楚)

rhel -name spa 自動 版本庫 留空 擴展 vat 因此

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

  1. class A {
  2. private:
  3. int a;
  4. int reserved[3];
  5. };
  6. class B {
  7. private:
  8. int a;
  9. quint32 b : 1;
  10. quint32 reserved : 31;
  11. };

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

  1. class Data {
  2. public:
  3. int a;
  4. };
  5. class A {
  6. private:
  7. Data *d_ptr;
  8. };

將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文件:

  1. QObjectData {
  2. public:
  3. QObject *q_ptr;
  4. ...
  5. };
  6. class Q_CORE_EXPORT QObject
  7. {
  8. ...
  9. Q_DECLARE_PRIVATE(QObject)
  10. public:
  11. Q_INVOKABLE explicit QObject(QObject *parent=0);
  12. virtual ~QObject();
  13. ...
  14. protected:
  15. QObject(QObjectPrivate &dd, QObject *parent = 0);
  16. ...
  17. protected:
  18. QScopedPointer<QObjectData> d_ptr;
  19. ...
  20. };

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

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

根據宏定義,則Q_DECLARE_PRIVATE(QObject)翻譯如下:

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


再來看看qGetPtrHelper的定義:

  1. template <typename T> static inline T *qGetPtrHelper(T *ptr)
  2. {
  3. return ptr;
  4. }

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

  1. QObject::QObject(QObject *parent)
  2. : d_ptr(new QObjectPrivate)
  3. {
  4. Q_D(QObject);
  5. d_ptr->q_ptr = this;
  6. ...
  7. }
  8. QObject::QObject(QObjectPrivate &dd, QObject *parent)
  9. : d_ptr(&dd)
  10. {
  11. Q_D(QObject);
  12. d_ptr->q_ptr = this;
  13. ...
  14. }

我們看第一個構造函數,對於根結點的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的實現情況:

頭文件:

  1. class Q_CORE_EXPORT QObjectPrivate : public QObjectData
  2. {
  3. Q_DECLARE_PUBLIC(QObject)
  4. ...
  5. };
  6. class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate
  7. {
  8. Q_DECLARE_PUBLIC(QWidget)
  9. ...
  10. };
  11. class Q_GUI_EXPORT QWidget : public QObject
  12. {
  13. ...
  14. Q_DECLARE_PRIVATE(QWidget)
  15. ...
  16. public:
  17. ...
  18. explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0);
  19. ...
  20. };

我們首先來看看Q_DECLARE_PUBLIC宏:

  1. #define Q_DECLARE_PUBLIC(Class) \
  2. inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
  3. inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
  4. friend class Class;

根據宏定義,Q_DECLARE_PUBLIC(QObject)翻譯如下:

  1. inline QObject *q_func()
  2. {
  3. return static_cast<QObject *>(q_ptr);
  4. }
  5. inline const QObject *q_func() const
  6. {
  7. return static_cast<const QObject *>(q_ptr);
  8. }
  9. friend class QObject;

Q_DECLARE_PUBLIC(QWidget)翻譯如下:

  1. inline QWidget *q_func()
  2. {
  3. return static_cast<QWidget *>(q_ptr);
  4. }
  5. inline const QWidget *q_func() const
  6. {
  7. return static_cast<const QWidget *>(q_ptr);
  8. }
  9. friend class QWidget;

註意這裏的q_ptr是在QObjectData裏公有聲明的,QObjectPrivate,QWidgetPrivate都派生或間接派生自QObjectData,所以可以訪問q_ptr。

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

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

接下來看看QWidget的構造函數的實現:

  1. QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
  2. : QObject(*new QWidgetPrivate, 0)
  3. {
  4. ...
  5. }


看到QObject(*new QwidgetPrivate, 0)這裏調用了QObject的第二個構造函數,將d_ptr指向new QWidgetPrivate所指向的堆內存。

https://blog.csdn.net/rabinsong/article/details/9474859

d指針在Qt上的應用及實現(有圖,很清楚)