QT中的D指针与Q指针

了解QT中引入D指针的初衷前需要了解下二进制兼容

1. 什么是二进制兼容

二进制兼容是针对动态链接库而言的,如果你的源代码已经编译成为可执行文件了,其中你调用了一些动态库,后来你为这个动态库进行了更新,并替换掉原来的动态库了,如果程序在运行时正常运行没有任何问题,不必重新编译使用这个库的可执行文件或使用这个库的其他库文件,那么这个库就是二进制兼容(二进制边界正常)。

2. 二进制不兼容会造成什么后果

如果库A升级没有能够做到二进制兼容,那么所有依赖它的程序(或库)都需要重新编译,否则会出现各种未知异常,其直接现象就是程序莫名其妙地挂掉。

3. C++如何生成可执行程序 

C++的ABI就是如何使用C++生成可执行程序的一张说明书。编译器会根据这个说明书,生成二进制代码。C++的ABI在不同的编译器下会略有不同。C++ABI的部分内容举例:

  • 函数参数传递的方式,比如 x86-64 用寄存器来传函数的前 4 个整数参数
  • 虚函数的调用方式,通常是 vptr/vtbl 然后用 vtbl[offset] 来调用
  • struct 和 class 的内存布局,通过偏移量来访问数据成员

4. 哪些常见做法会破坏二进制兼容

改变了虚函数的offset或者成员的顺序

  • 添加新的虚函数,会造成 vtbl 里的排列变化。(不要考虑“只在末尾增加”这种取巧行为,因为你的 class 可能已被继承。)
  • 不导出或者移除一个导出类
  • 改变类的继承
  • 改变虚函数声明时的顺序(偏移量改变,导致调用失败)
  • 添加新的非静态成员变量(类的内存布局改变,偏移量也发生变化)
  • 改变非静态成员变量的声明顺序
  • 增加默认模板类型参数
  • 改变 enum 的值,把 enum Color { Red = 3 }; 改为 Red = 4。这会造成错位。当然,由于 enum 自动排列取值,添加 enum 项也是不安全的,除非是在末尾添加
  • 给函数增加默认参数,现有的可执行文件无法传这个额外的参数

4. 哪些做法多半不会破坏二进制兼容

  • 添加非虚函数(包括构造函数)
  • 添加新的类
  • 添加Qt中的信号槽
  • 在已存在的枚举类型中添加一个枚举值
  • 添加新的静态成员变量
  • 修改成员变量名称(偏移量未改变)
  • 添加Q_OBJECT,Q_PROPERTYQ_ENUMS ,Q_FLAGS宏,添加这些宏都是修改了moc生成的文件,而不是类本身

一种做法是预先分配若干个保留空间,当要添加项时,使用保留项。但是这种做法很呆板,因为你不知道未来到底会有多少扩展项,少了不满足要求,多了浪费空间。

QT引入D指针就是为了最大程度上实现二进制兼容,将类A中的成员变量放入Data 中,A中放入Data的一个指针,这样的话,无论你向Data中添加多少数据,A的对象模型中Data指针始终是4个字节的大小,这个Data指针就是QT中的d_ptr指针。

 

QT中的D指针

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;  
    ...  
}; 

先看下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;  

展开后

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函数定义(d_ptr可以是智能指针(例如QScopedPointer),在这种情况下它不能只传递给reinterpret_cast:d_func()必须使用成员函数访问内部指针,qGetPtrHelper所做的是触发智能指针的隐式转换,当作为参数传递给原始指针时,从而消除了对特殊处理的需要.)

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

d_ptr在QObject的构造函数中初始化

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;  
    ...  
}  

其中Q_D宏定义如下:

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

Q_D(QObject)定义为:QObjectPrivate * const d = d_func();

定义了一个QObjectPrivate的常量指针,指向d_func() 的返回值,而该返回值,正是d_ptr,因此同过Q_D宏我们就可以访问d指针了。

/* widget.h */
class  Widget {
public:
   Widget();
    ...
protected:
   // 只有子类会访问以下构造函数
   Widget(WidgetPrivate &d);// 允许子类通过它们自己的私有结构体来初始化
   WidgetPrivate *d_ptr;
};
 
 /* widget_p.h */ 
 struct  WidgetPrivate {
     WidgetPrivate(Widget *q) : q_ptr(q) { } 
     Widget *q_ptr; 
     Rect geometry;
     String stylesheet;
 };
 
/* widget.cpp */
Widget::Widget()
  : d_ptr(new WidgetPrivate(this)) {
}
  
Widget::Widget(WidgetPrivate &d)
  : d_ptr(&d) {
}
 
/* label.h */
class Label :public Widget {
public:
   Label();
    ...
protected:
   Label(LabelPrivate &d);// 允许Label的子类通过它们自己的私有结构体来初始化
   //  注意Label在这已经不需要d_ptr指针,它用了其基类的d_ptr
};
 
/* label.cpp */
#include "widget_p.h" 
 
class LabelPrivate :public WidgetPrivate {
public:
    String text;
};
 
Label::Label()
   : Widget(*new LabelPrivate)//用其自身的私有结构体来初始化d指针
}
  
Label::Label(LabelPrivate &d)
   : Widget(d) {
}

为了防止每产生一个Label对象,就会为相应的LabelPrivate和WidgetPrivate分配空间,当遇到像QListWidget(此类在继承结构上有6层深度),就会为相应的Private结构体分配6次空间。以上代码部分允许使用继承类的D指针来初始化基类D指针,比如当我们建立一个Label对象时,它就会建立相应的LabelPrivate结构体(其是WidgetPrivate的子类)。它将其d指针传递给Widget的保护构造函数。这时,建立一个Label对象仅需为其私有结构体申请一次内存。Label同样也有一个保护构造函数可以被继承Label的子类使用,以提供自己对应的私有结构体。

而前面一步优化导致的副作用是Label中使用的q-ptr和d-ptr分别是Widget和WidgetPrivate类型。这就意味着d_ptr->text = text的操作是不起作用的。这个时候正是reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr))这个函数发挥作用的时候了,reinterpret_cast运算符是用来处理无关类型之间的转换,它会产生一个新的值,这个值会有与原始参数有完全相同的比特位,常用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。

 

QT中的Q指针

QT中有D指针来存放成员变量了,很多时候ClassPrivate可能需要访问Class类中的成员或成员函数,比如LabelPrivate可能会有getLinkTargetFromPoint()(helper函数)以当按下鼠标时去找到相应的链接目标。在很多场合,这些helper函数需要访问公有类,例如访问一些属于Label类或是其基类Widget的函数。此时ClassPrivate中需要一个指针指向Class类,这个指针就是Q指针,比如WidgetPrivate中的Widget *q_ptr;
 

class Q_CORE_EXPORT QObjectPrivate : public QObjectData  
{  
    Q_DECLARE_PUBLIC(QObject)  
    ...  
};  
  
class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate  
{  
    Q_DECLARE_PUBLIC(QWidget)  
    ...  
};  

看下Q_DECLARE_PUBLIC(QObject)

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

展开后

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_ptr是在QObjectData里公有声明的,QObjectPrivate,QWidgetPrivate都派生或间接派生自QObjectData,所以可以访问q_ptr。

使用Q_Q宏就可以使用q指针了

#define Q_Q(Class) Class * const q = q_func();

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt使用d指针和q指针来实现封装和隐藏内部实现细节,以及提供更好的代码可读性和可维护性。同时,Qt还使用命名空间来组织和管理类和函数。 下面是一个示例代码,演示了Qt如何使用d指针、q指针和命名空间: ```cpp // counter.h #ifndef COUNTER_H #define COUNTER_H #include <QObject> namespace MyNamespace { class CounterPrivate; // 前向声明 class Counter : public QObject { Q_OBJECT public: explicit Counter(QObject *parent = nullptr); ~Counter(); void increment(); void decrement(); int value() const; private: CounterPrivate *d_ptr; // d指针 }; } #endif // COUNTER_H // counter.cpp #include "counter.h" namespace MyNamespace { class CounterPrivate { public: int count; }; Counter::Counter(QObject *parent) : QObject(parent), d_ptr(new CounterPrivate) { d_ptr->count = 0; } Counter::~Counter() { delete d_ptr; } void Counter::increment() { ++d_ptr->count; } void Counter::decrement() { --d_ptr->count; } int Counter::value() const { return d_ptr->count; } } // main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include "counter.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); MyNamespace::Counter counter; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("Counter", &counter); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); } ``` 在这个示例,我们定义了一个名为Counter的类,它位于MyNamespace命名空间。Counter类使用了d指针来隐藏内部实现细节,并提供了increment、decrement和value等公共接口来操作计数器的值。在main.cpp,我们创建了Counter的实例,并将其作为上下文属性传递给QML引擎。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值