Qt內部的d指針和q指針手把手教你實現


Qt內部的d指針和q指針

在講Qt的D指針之前讓我們來簡單的解釋一下D指針出現的目的,目的是什么呢?保證模塊間的二進制兼容

什么是二進制兼容呢,簡單說就是如果自己的程序使用了第三方模塊,二進制兼容可以保證在修改了第三方模塊之后,也就是已經改變了內存布局之后,自己的程序可以不用重新編譯就能夠兼容修改后的第三方模塊。 二進制指的是編譯生成的.so或者dll庫,一旦程序編譯好之后類的內存布局就確定了,兼容性值得就是即使內存布局被改變也依然能夠通過原來的內存布局找到對應的成員變量,比較官方的解釋是:

二進制兼容:在升級庫文件的時候,不必重新編譯使用此庫的可執行文件或其他庫文件,並且程序的功能不被破壞。

一些詳細的介紹可以參考這個博文

之前轉載的一篇文章介紹了多態場景下基類和子類的內存布局。

當我們知道二進制兼容這個問題存在之后,在沒有參考Qt等解決方案之前,用我們自己聰明的小腦袋瓜子想一想怎么解決這個問題?調整私有成員變量的順序會造成二進制不兼容,那我們把一個類的私有成員單獨拿出來封裝成一個只有成員變量的類,然后用一個指針指向這個類對象是不是就可以了呢?很好,這種解決問題的思路很好,比直接百度不思考要強多了。

馬斯克為什么這么厲害,我記得在一個采訪中他提到了為什么自己這么牛逼,什么事情都敢干,回答是因為自己堅信第一性原理這個理論。簡單闡述就是:

如果一件事從理論上是可行的,那就可以去干

那我們得到啟發之后回到這個問題本身,已經有了對二進制兼容的定義,我們根據上面的分析得出結論,用指針的方式實現不就可以規避二進制不兼容的情況了嗎?我們先動手嘗試完成一個自己腦子里的第一版實現:

  • widget.h

    class WidgetPrivate; class Widget { public: Widget():d_ptr(new WidgetPrivate){ d_ptr->q_ptr = this; }//1 ~Widget(){ if(d_ptr) delete d_ptr; } inline void setWidth(int width) { d_ptr->width = width; } inline int getWidth() { return d_ptr->width; } protected: WidgetPrivate* d_ptr = nullptr; } 
  • widgetPrivate.h

    class Widget; class WidgetPrivate { public: WidgetPrivate(){} ~WidgetPrivate(){} int width; int height; Widget* q_ptr; } 

1處的代碼可以直接設置q指針的方式比較優雅,不然我們要修改widgetPrivate(Widget* widget): q_ptr(widget){}為這樣的構造函數,使用的時候

Widget():d_ptr(new WidgetPrivate(this)),顯然這種方式不夠優雅。這樣的話classPrivate類就看着非常干凈,甚至把class替換成struct都可以~

總的來說看起來很完美,widgetPrivate作為私有類我們在改動的時候並不會破壞widget的二進制兼容性。然后呢,夠了嗎?我們知道Qt的GUI類是對象樹的結構,存在着多層次繼承結構(QObject<-QWidget<-QLabel ...),也在此基礎上實現了內存半自動化管理的機制。我們如果加一個子類呢?動手試試

  • Label.h

    class LabelPrivate; class Label:public Widget { public: Lable():d_ptr(new LabelPrivate){ d_ptr->q_ptr = this; } ~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){ d_ptr->icon = icon; } inline std::string getIcon() { return d_ptr->icon; } protected: LabelPrivate* d_ptr = nullptr; } 
  • LabelPrivate.h

    class Label; class LabelPrivate { public: LabelPrivate(){} ~LabelPrivate(){} std::string icon; Label* q_ptr = nullptr; private: } 
  • 使用

            Label label;
            label.setWidth(65); label.setHeight(100); label.setIcon("d:/image/prettyGirl"); 

    我們把構造函數的過程打印出來

    WidgetPrivate::WidgetPrivate()

    Widget::Widget()

    WidgetPrivate::WidgetPrivate()

    LabelPrivate::LabelPrivate()

    Label::Label()

    我們可以看到WidgetPrivate::WidgetPrivate()構造函數被調用了兩次,因為子類Label也有一個d_ptr指針。這還是只有一層繼承結構的時候,每多一層繼承結構都要平白無故的增添一個BaseClassPrivate對象的構造,空間成本浪費,我們要進行針對性的改造。

    1. 我們是不是可以復用基類里面的d_ptr
    2. 這樣的話我們要去掉子類里面的d_ptr
    3. 我們要用WidgetPrivate* ->LabelPrivate *就要使用c++多態的性質,所以要構造必要條件
      1. 繼承
      2. 虛函數
  • Widget.h

    class WidgetPrivate; class Widget { public: Widget():d_ptr(new WidgetPrivate){ d_ptr->q_ptr = this; } ~Widget(){ if(d_ptr) delete d_ptr; } inline void setWidth(int width) { WidgetPrivate* d = reinterpret_cast<WidgetPrivate*>(d_ptr); d->width = width; } inline int getWidth() { WidgetPrivate* d = reinterpret_cast<WidgetPrivate*>(d_ptr); return d->width; } protected: Widget(WidgetPrivate& wprivate):d_ptr(&wprivate){}//[1] WidgetPrivate* d_ptr = nullptr; } 
  • Label.h

    class LabelPrivate; class Label:public Widget { public: Lable():Widget(*new LabelPrivate){ d_ptr->q_ptr = this; }//[2] ~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){ LabelPrivate* d = reinterpret_cast<LabelPrivate*>(d_ptr);//[3] d_ptr->icon = icon; } inline std::string getIcon() { LabelPrivate* d = reinterpret_cast<LabelPrivate*>(d_ptr); return d_ptr->icon; } protected: Label(LabelPrivate& lprivate):d_ptr(&lprivate){}//[4] } 
  • WidgetPrivate.h

    class Widget; class WidgetPrivate { public: WidgetPrivate(){} virtual ~WidgetPrivate(){}//[5] int width; int height; Widget* q_ptr; } 
  • LabelPrivate.h

    class Label; class LabelPrivate:public WidgetPrivate//[6] { public: LabelPrivate(){} ~LabelPrivate(){} std::string icon; Label* q_ptr = nullptr; private: } 

此版本包含幾個修改點:

  1. 公開類添加一個protected級別的構造函數,用於子類構造的時候在初始化參數列表來初始化基類,這里實現了多態的特性。
  2. 公開類的子類構造函數的初始化參數列表不再初始化d_ptr,而是調用基類的帶參構造函數,實參為*new LabelPrivate,跟[1]配合實現了多態性。
  3. d指針轉換成子類型的私有類才都調用相關的方法。
  4. Label子類也要實現一個protected保護級別的構造函數,因為Label也可能會被繼承。
  5. WidgetPrivate.h私有基類的析構函數定義為virtual,這樣在釋放資源的時候才能夠不漏掉LabelPrivate的釋放。
  6. LabelPrivate繼承WidgetPrivate,構成多態的基礎條件。

Ok,到這里就基本完成了,可以不做修改的替換掉Qt的那一套d指針和q指針,哈哈哈(有點扯了。)論實用程度是夠了,但是論優雅程度跟Qt原生的還是有一定距離,我們添加一些語法糖和c++11的智能指針來優化一下。

  • global.h

    #define D_D(Class) Class##Private* d = reinterpret_cast<Class##Private*>(d_ptr.get()); #define Q_D(Class) Class* q = reinterpret_cast<Class*>(d_ptr.get()); 
  • label.h

    class LabelPrivate; class Label:public Widget { public: Lable():Widget(*new LabelPrivate){ d_ptr->q_ptr = this; } ~Lable(){ if(d_ptr) delete d_ptr; } inline void setIcon(std::string icon){ D_D(Label)//[1] d->icon = icon; } inline std::string getIcon() { D_D(Label)//[2] return d->icon; } protected: Label(LabelPrivate& lprivate):d_ptr(&lprivate){} } 

    是不是優雅了很多.

    智能指針的話只需要替換Widget::d_ptr為std::unique_ptr<Widget>()就可以了。可以收工了~

 
分類:  Qt學習大全


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM