二进制兼容 QT的源文件中,包含许多Q_D
和Q_Q
宏,这两个宏是d指针(d-pointer
,也称不透明指针,the opaque pointer)的设计模式的一部分,其可以做到:
对用户隐藏库的实现细节;
实现二进制兼容
(可以在不破坏二进制兼容性的情况下,对库进行实现的更改)。
什么是二进制兼容?什么是源代码兼容? 一个动态链接到旧版本库下运行的程序,在无需经过重新编译的情况下,仍然能够在新版本库下继续运行,这种库称为二进制兼容动态库
; 一个动态链接到旧版本库下运行的程序,在不需要修改程序源代码,但需要经过重新编译的情况下,才可以在新版本的库下继续运行,这种库称为源代码兼容
动态库;如何做到二进制兼容? 要使得一个dll
或so
能做到二进制兼容,就要求其中的每一个结构和每一个对象的数据模型保持不变 (如增加或删除类的数据成员,就会影响对象的数据模型),若发生变动,其会导致原有的数据成员在对象数据模型中的位移发生变化,那么编译后的新版本库很大概率会使得程序运行崩溃。 因此,为了使得增删类数据成员后对象数据模型也不会发生变化,通常有以下做法:
预先分配若干保留空间以备用(做法死板,因为不知道未来需要扩展多少项,同时也浪费空间)
将类A的数据成员放到另一个类B中,类A引入类B的一个指针(做法灵活,无论类B是否添加或删除了数据成员,类A中的数据成员始终都只有指向类B的指针的四个字节大小) QT中,为了实现二进制兼容,绝大多数类都是采用第二种做法。
d-pointer
参考:https://wiki.qt.io/D-Pointer
假设有2个类,分别为Widget类和Button类,其中Button类继承自Widget类。代码如下:
1 2 3 4 5 6 7 8 9 10 class Widget { public : Widget (); ~Widget (); int width () ; void setWidth (int w) ; private : int width; };
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "Widget.h" #include <iostream> Widget::Widget () { std::cout << "Widget is created." << std::endl; } Widget::~Widget () { std::cout << "Widget is destroyed." << std::endl; } int Widget::width () { return width; }void Widget::setWidth (int w) { width = w; }
1 2 3 4 5 6 7 8 9 10 class Button : public Widget{ public : Button (); ~Button (); std::string text () ; void setText (std::string t) ; private : std::string text; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> Button::Button () { std::cout << "Button is created." << std::endl; } Button::~Button () { std::cout << "Button is destroyed." << std::endl; } std::string Button::text () { return text; }void Button::setText (std::string t) { text = t; }
编译上述程序后,得到demoLib_1.0.dll
; 此时使用程序DemoApp调用Button::getText(),程序正常运行。 在下一个版本中,Widget类中将新增一个变量height:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Widget { public : Widget (); ~Widget (); int width () ; void setWidth (int w) ; int height () ; void setHeight (int h) ; private : int width; int height; };
编译上述程序后,得到demoLib_1.1.dll
; 此时使用程序DemoApp去调用Button::getText(),当程序未重新编译时,会导致程序崩溃。 为什么程序运行崩溃了? Widget类添加了新数据成员,将改变Widget类和Button类对象的大小。 因为C++编译器生成代码时,使用偏移量来访问对象中的数据。 Button对象在两个版本的(简化)内存结构如下:
偏移量
0
1
2
demoLib_1.0
width
text
demoLib_1.1
width
height
text
在1.0中,text位于偏移量1的逻辑位置,程序调用Button::getText(),返回偏移量为1的数据成员;
在1.1中,text位于偏移量2的逻辑位置,程序没有重新编译,使用旧的内存布局,调用Button::getText(),返回偏移量为1的数据成员,进而导致程序崩溃。
因此,当库文件发布后,不要改变导出的C++类的大小和数据成员位置。 那么,如何在不改变对象大小及成员位置的同时添加新功能呢? 为了使得库中被调用的公共类的内存布局不发生改变,我们可以让所有的公共类都只持有一个私有的指针变量,这个指针变量指向一个包含所有数据的私有类(或结构体等),该私有类可以在未来的版本中任意修改数据成员,而不影响到使用了这个公共类的程序。外部无法使用这个私有类,只能使用公有类,且公有类只持有一个指向私有类的指针变量,该指针就被称为D指针
;
Widget.h1 2 3 4 5 6 7 8 9 10 11 12 13 class WidgetPrivate ;class Widget { public : Widget (); ~Widget (); int width () ; void setWidth (int w) ; int height () ; void setHeight (int h) ; private : WidgetPrivate* d_ptr; };
Widget.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "A.h" #include "Widget.h" #include <iostream> #include "WidgetPrivate.h" Widget::Widget () : d_ptr (new WidgetPrivate ()) { std::cout << "Widget is created." << std::endl; } Widget::~Widget () { if (d_ptr) { delete d_ptr; } std::cout << "Widget is destroyed." << std::endl; } int Widget::width () { return d_ptr->width; }void Widget::setWidth (int w) { d_ptr->width = w; }int Widget::height () { return d_ptr->height; }void Widget::setHeight (int h) { d_ptr->height = h; }
WidgetPrivate.h1 2 3 4 5 6 7 8 9 class WidgetPrivate { public : WidgetPrivate (); ~WidgetPrivate (); public : int width; int height; };
WidgetPrivate.cpp1 2 3 4 5 6 7 8 9 10 11 #include "WidgetPrivate.h" #include <iostream> WidgetPrivate::WidgetPrivate () : width (100 ), height (40 ) { std::cout << "WidgetPrivate is created." << std::endl; } WidgetPrivate::~WidgetPrivate () { std::cout << "WidgetPrivate is destroyed." << std::endl; }
Button.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <string> #include "Widget.h" class ButtonPrivate ;class Button : public Widget{ public : Button (); ~Button (); std::string text () ; void setText (std::string t) ; private : ButtonPrivate* d_ptr; };
Button.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> Button::Button () : d_ptr (new ButtonPrivate) { std::cout << "Button is created." << std::endl; } Button::~Button () { if (d_ptr) { delete d_ptr; } std::cout << "Button is destroyed." << std::endl; } std::string Button::text () { return d_ptr->text; }void Button::setText (std::string t) { d_ptr->text = t; }
ButtonPrivate.h1 2 3 4 5 6 7 8 9 10 #include <string> class ButtonPrivate { public : ButtonPrivate (); ~ButtonPrivate (); public : std::string text; };
ButtonPrivate.cpp1 2 3 4 5 6 7 8 9 10 11 #include "ButtonPrivate.h" #include <iostream> ButtonPrivate::ButtonPrivate () { std::cout << "ButtonPrivate is created." << std::endl; } ButtonPrivate::~ButtonPrivate () { std::cout << "ButtonPrivate is destroyed." << std::endl; }
main.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include "Button.h" int main () { Button btn; btn.setText ("世界和平" ); std::cout << "****************************" << std::endl; std::cout << "btn.width: " << btn.width () << std::endl; std::cout << "btn.height: " << btn.height () << std::endl; std::cout << "btn.text: " << btn.text () << std::endl; std::cout << "****************************" << std::endl; return 0 ; }
控制台输出1 2 3 4 5 6 7 8 9 10 11 12 13 WidgetPrivate is created. Widget is created. ButtonPrivate is created. Button is created. **************************** btn.width: 100 btn.height: 40 btn.text: 世界和平 **************************** ButtonPrivate is destroyed. Button is destroyed. WidgetPrivate is destroyed. Widget is destroyed.
如此,调用该库的程序就无法直接访问到D指针,且私有类中的数据都可以随意更改。
d-pointer的优势
二进制兼容;
因此实现细节:只需发布库的头文件和二进制文件,源文件可以闭源;
可作为API:头文件没有任何实现的细节,可以作为API;
编译速度更快:头文件中的实现细节都被移动到源文件中,加快了编译速度。
q-pointer 目前,公共类的D指针指向了私有类,在实际应用中,私有类中的私有函数通常需要调用公共类的属性或方法,为了解决这个问题,此时我们也需要在私有类中引入一个指向该公有对象 的指针,该指针被称为Q指针
。
Widget.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class WidgetPrivate ;class Widget { public : Widget (); ~Widget (); int width () ; void setWidth (int w) ; int height () ; void setHeight (int h) ; void update () ; private : WidgetPrivate* d_ptr; };
Widget.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include "Widget.h" #include <iostream> #include "WidgetPrivate.h" Widget::Widget () : d_ptr (new WidgetPrivate) { d_ptr->q_ptr = this ; std::cout << "Widget is created." << std::endl; } Widget::~Widget () { if (d_ptr) { delete d_ptr; } std::cout << "Widget is destroyed." << std::endl; } int Widget::width () { return d_ptr->width; }void Widget::setWidth (int w) { d_ptr->width = w; }int Widget::height () { return d_ptr->height; }void Widget::setHeight (int h) { d_ptr->height = h; }void Widget::update () { std::cout << "Widget::update() is running." << std::endl; }
WidgetPrivate.h1 2 3 4 5 6 7 8 9 10 11 class Widget ;class WidgetPrivate { public : WidgetPrivate (); ~WidgetPrivate (); public : Widget* q_ptr; int width; int height; };
WidgetPrivate.cpp1 2 3 4 5 6 7 8 9 10 11 12 #include "WidgetPrivate.h" #include "Widget.h" #include <iostream> WidgetPrivate::WidgetPrivate () : width (100 ), height (40 ) { std::cout << "WidgetPrivate is created." << std::endl; } WidgetPrivate::~WidgetPrivate () { std::cout << "WidgetPrivate is destroyed." << std::endl; }
Button.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <string> #include "Widget.h" class ButtonPrivate ;class Button : public Widget{ public : Button (); ~Button (); std::string text () ; void setText (std::string t) ; void paint () ; private : ButtonPrivate* d_ptr; };
Button.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> Button::Button () : d_ptr (new ButtonPrivate) { d_ptr->q_ptr = this ; std::cout << "Button is created." << std::endl; } Button::~Button () { if (d_ptr) { delete d_ptr; } std::cout << "Button is destroyed." << std::endl; } std::string Button::text () { return d_ptr->text; }void Button::setText (std::string t) { d_ptr->text = t; }void Button::paint () { d_ptr->q_ptr->update (); }
ButtonPrivate.h1 2 3 4 5 6 7 8 9 10 11 12 #include <string> class Button ;class ButtonPrivate { public : ButtonPrivate (); ~ButtonPrivate (); public : Button* q_ptr; std::string text; };
ButtonPrivate.cpp1 2 3 4 5 6 7 8 9 10 11 12 #include "ButtonPrivate.h" #include "Button.h" #include <iostream> ButtonPrivate::ButtonPrivate () { std::cout << "ButtonPrivate is created." << std::endl; } ButtonPrivate::~ButtonPrivate () { std::cout << "ButtonPrivate is destroyed." << std::endl; }
main.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include "Button.h" int main () { Button btn; btn.setText ("世界和平" ); btn->paint (); std::cout << "****************************" << std::endl; std::cout << "btn.width: " << btn.width () << std::endl; std::cout << "btn.height: " << btn.height () << std::endl; std::cout << "btn.text: " << btn.text () << std::endl; std::cout << "****************************" << std::endl; return 0 ; }
控制台输出1 2 3 4 5 6 7 8 9 10 11 12 13 14 WidgetPrivate is created. Widget is created. ButtonPrivate is created. Button is created. Widget::update() is running. **************************** btn.width: 100 btn.height: 40 btn.text: 世界和平 **************************** ButtonPrivate is destroyed. Button is destroyed. WidgetPrivate is destroyed. Widget is destroyed.
另外一种构造直接传参赋值给q_ptr的写法:
1 2 3 4 WidgetPrivate (Widget* w) : q_ptr (w) {}Widget () : d_ptr (new WidgetPrivate (this )) {}
使用继承来优化d-pointer 由于每一个公有类都存在一个指向私有类对象的指针,因此创建公有类对象的同时就会创建私有类对象。上述例子中,创建了一个Button
对象,会同时创建ButtonPrivate
和WidgetPrivate
,如果继承层级多的情况下,将会自下往上创建私有类的内存分配。 为了解决这个问题,我们可以:保留基类的d_ptr,去除子类的d_ptr,并让子公共类自下而上传递d_ptr,这样做的前提是私有类必须有继承关系。
私有类需要具有多层继承关系;
去掉子公共类的d_ptr,只保留基公共类的d_ptr;
去掉子私有类的q_ptr,只保留基私有类的q_ptr;
所有公共类加入protected权限的构造函数,允许子公共类的构造函数传递子私有类对象来初始化父公共类的d_ptr,将该d_ptr一层层传递到最顶层。
1 2 3 4 5 6 7 8 9 10 11 class Widget ;class WidgetPrivate { public : WidgetPrivate (); virtual ~WidgetPrivate (); public : Widget* q_ptr; int width; int height; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "WidgetPrivate.h" #include "Widget.h" #include <iostream> WidgetPrivate::WidgetPrivate (): width (100 ), height (40 ) { std::cout << this << "--WidgetPrivate is created." << std::endl; } WidgetPrivate::~WidgetPrivate () { std::cout << this << "--WidgetPrivate is destroyed." << std::endl; }``` ```c++ #include <string> #include "WidgetPrivate.h" class Button ;class ButtonPrivate : public WidgetPrivate{ public : ButtonPrivate (); virtual ~ButtonPrivate (); public : std::string text; };
1 2 3 4 5 6 7 8 9 10 11 12 #include "ButtonPrivate.h" #include "Button.h" #include <iostream> ButtonPrivate::ButtonPrivate () { std::cout << this << "--ButtonPrivate is created." << std::endl; } ButtonPrivate::~ButtonPrivate () { std::cout << this << "--ButtonPrivate is destroyed." << std::endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class WidgetPrivate ;class Widget { public : Widget (); ~Widget (); int width () ; void setWidth (int w) ; int height () ; void setHeight (int h) ; void update () ; protected : Widget (WidgetPrivate &d); WidgetPrivate* d_ptr; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "Widget.h" #include <iostream> #include "WidgetPrivate.h" Widget::Widget () : d_ptr (new WidgetPrivate) { d_ptr->q_ptr = this ; std::cout << this << "--Widget 1 is created." << std::endl; } Widget::Widget (WidgetPrivate &d) : d_ptr (&d) { std::cout << this << "--Widget 2 is created." << std::endl; } Widget::~Widget () { if (d_ptr) { delete d_ptr; } std::cout << this << "--Widget is destroyed." << std::endl; } int Widget::width () { return d_ptr->width; }void Widget::setWidth (int w) { d_ptr->width = w; }int Widget::height () { return d_ptr->height; }void Widget::setHeight (int h) { d_ptr->height = h; }void Widget::update () { std::cout << "Widget::update() is running." << std::endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <string> #include "Widget.h" class ButtonPrivate ;class Button : public Widget{ public : Button (); ~Button (); std::string text () ; void setText (std::string t) ; void paint () ; protected : Button (ButtonPrivate &d); };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> Button::Button () : Widget (*new ButtonPrivate) { std::cout << this << "--Button 1 is created." << std::endl; } Button::Button (ButtonPrivate &d) : Widget (d) { std::cout << this << "--Button 2 is created." << std::endl; } Button::~Button () { if (d_ptr) { delete d_ptr; } std::cout << this << "--Button is destroyed." << std::endl; } std::string Button::text () { ButtonPrivate *d = reinterpret_cast <ButtonPrivate*>(d_ptr); return d->text; } void Button::setText (std::string t) { ButtonPrivate *d = reinterpret_cast <ButtonPrivate*>(d_ptr); d->text = t; } void Button::paint () { d_ptr->q_ptr->update (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include "Button.h" int main () { Button btn; btn.setText ("世界和平" ); btn.paint (); std::cout << "****************************" << std::endl; std::cout << "btn.width: " << btn.width () << std::endl; std::cout << "btn.height: " << btn.height () << std::endl; std::cout << "btn.text: " << btn.text () << std::endl; std::cout << "****************************" << std::endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 0x807700--WidgetPrivate is created. 0x807700--ButtonPrivate is created. 0x60fe94--Widget 2 is created. 0x60fe94--Button 1 is created. Widget::update() is running. **************************** btn.width: 100 btn.height: 40 btn.text: 世界和平 **************************** 0x807700--ButtonPrivate is destroyed. 0x807700--WidgetPrivate is destroyed. 0x60fe94--Button is destroyed.
使用宏来优化d-pointer和q-pointer 在上面的例子中,d_ptr是基公共类的指针,因此子公共类想要使用d_ptr获取独有的成员变量时,就需要强转为适当的类型。
1 ButtonPrivate *d = reinterpret_cast <ButtonPrivate*>(d_ptr);
但是,这么长一串并不美观而且重复。 因此,我们可以定义两个宏来代替强转语句。新建global.h,写入以下语句:
1 2 3 4 #define W_D(Class) Class##Private* d = reinterpret_cast<Class##Private*> (d_ptr); #define W_Q(Class) Class* q = reinterpret_cast<Class*> (d_ptr);
Button.cpp的代码就可以改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> #include <QObject> Button::Button () : Widget (*new ButtonPrivate) { std::cout << this << "--Button 1 is created." << std::endl; } Button::Button (ButtonPrivate &d) : Widget (d) { std::cout << this << "--Button 2 is created." << std::endl; } Button::~Button () { std::cout << this << "--Button is destroyed." << std::endl; } std::string Button::text () { W_D (Button) return d->text; } void Button::setText (std::string t) { W_D (Button) d->text = t; } void Button::paint () { d_ptr->q_ptr->update (); }
【TODO】Qt中的d-pointer 在Qt中,绝大部分公共类都使用d指针方案。 唯一一种特殊情况是,事先知晓类永远不会添加额外的成员变量,才会将数据成员直接存储在类本身,如Qpoint,QRect类。 在Qt中,所有私有对象的基类都是QObjectPrivate
。
Qt也是在src/corelib/global/qglobal.h
中定义了两个宏,使得强转书写更简单;
宏:Q_D(Class) 1 #define Q_D(Class) Class##Private * const d = d_func()
d_func() 是内联函数,它返回指向 ClassPrivate 的指针;
宏:W_DECLARE_PRIVATE(Class) 1 2 3 4 5 6 #define W_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;
宏 W_DECLARE_PRIVATE 会在类的定义中声明 d_func() 方法,以便返回一个指向私有类 ClassPrivate 的指针; 通过使用 reinterpret_cast 对 d_ptr 进行类型转换,并调用 qGetPtrHelper 函数来处理指针的获取。 friend class Class##Private; 声明使得 ClassPrivate 可以访问 Class 的私有成员。
函数:qGetPtrHelper() 1 2 3 4 template <typename T> inline T *qGetPtrHelper (T *ptr) { return ptr; }template <typename Ptr> inline auto qGetPtrHelper (Ptr &ptr) -> decltype (ptr.operator ->()) { return ptr.operator ->(); }
qGetPtrHelper 函数是一个辅助模板函数,目的是提供一种安全的方式来获取指向对象的指针。
宏:Q_Q(Class) 1 #define Q_Q(Class) Class * const q = q_func()
q_func() 是内联函数,它返回指向 Class 的指针;
宏:Q_DECLARE_PUBLIC(Class) 用于在私有类中访问其公共类的指针。它的作用是简化在私有实现中获取公共接口的指针。可以让私有实现类(例如 ClassPrivate)在需要时方便地访问其对应公共类(例如 Class)的功能。它通常与 Q_Q(Class) 宏一起使用,后者用于在公共接口中获取指向私有实现的指针。
1 2 3 4 #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;
global.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef GLOBAL_H #define GLOBAL_H template <typename T> inline T *qGetPtrHelper (T *ptr) { return ptr; }template <typename Ptr> inline auto qGetPtrHelper (Ptr &ptr) -> decltype (ptr.operator ->()) { return ptr.operator ->(); }#define W_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; #define W_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; #define W_D(Class) Class##Private * const d = d_func() #define W_Q(Class) Class * const q = q_func() #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <string> #include "Widget.h" class ButtonPrivate ;class Button : public Widget{ public : Button (); ~Button (); std::string text () ; void setText (std::string t) ; void paint () ; protected : Button (ButtonPrivate &d); W_DECLARE_PRIVATE (Button) };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include "Button.h" #include "ButtonPrivate.h" #include <iostream> Button::Button () : Widget (*new ButtonPrivate) { std::cout << this << "--Button 1 is created." << std::endl; } Button::Button (ButtonPrivate &d) : Widget (d) { std::cout << this << "--Button 2 is created." << std::endl; } Button::~Button () { if (d_ptr) { delete d_ptr; } std::cout << this << "--Button is destroyed." << std::endl; } std::string Button::text () { W_D (Button); return d->text; } void Button::setText (std::string t) { W_D (Button); d->text = t; } void Button::paint () { d_ptr->q_ptr->update (); }