在查看 QT 源码的过程中,QT 无处不在的 Q_D, Q_Q 宏,在看源码过程中可能需要先理解,才能更好的继续下去
例如 QWidget 类, 查看它的头文件 QWidget.h 时能看见一个前置声明 class QWidgetPrivate; 以及 Q_DECLARE_PRIVATE(QWidget),同时在头文件中也没有看到任何的成员变量
此外以头文件中的 public 函数 int x() const; 为例, 查看它的实现源码
int QWidget::x() const
{
Q_D(const QWidget);
if (isWindow() && ! (windowType() == Qt::Popup))
return data->crect.x() - d->frameStrut().left();
return data->crect.x();
}
可以看到 Q_D 的宏, 以及一个 d 的指针, 第一次接触的时候一脸懵逼, 完全不知道如何下手
在解释这些问题的时候, 首先需要知道 QT 引入 Q_D, Q_Q 等宏的原因,和它设计的思路
二进制兼容性 和 PIML
https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C%2B%2B (我对这篇文章做了翻译,翻译文章地址 二进制兼容性问题C++策略 )
首先可以看一下这篇文章,这里面解释了什么是 二进制兼容性, 简单点说就是 用新版本的库替换掉程序中旧版本库, 程序仍然可以正常运行
如果不支持 二进制兼容性,替换库之后,可能会在程序运行时用到这个库的时候程序异常崩溃和闪退,而解决这个问题, 一般情况是将所有依赖这个库的相关的库和程序全部重新编译,替换才能保证程序正常运行。
简单点说,d指针的引入主要有以下的几个好处:
- 隐藏了所有的实现的细节,仅仅开放了被调用的接口
- 私有的结构体可以随意更改,仅仅只需要重新编译被更改的库,而不需要重新编译整个工程项目
理解D指针
首先看一下 Q_D 的宏定义
#define Q_D(Class) Class##Private * const d = d_func()
简单介绍一下 宏定义下的 # 和 ## 的用法
#是把宏参数变成一个字符串##是将2个宏参数连接在一起
回头看一下 Q_D(Class) 的意思就是 ClassPrivate * const d = d_func()
接下来看一下 d_func() 函数的具体实现, 同样是在 qglobal.h的文件中:
template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }
#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;
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(Dptr); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } \
friend class Class##Private;
#define Q_D(Class) Class##Private * const d = d_func()
d_func() 函数实际上就是返回了一个 ClassPrivate 类型的指针
可以这样理解,以 QWidget 为例
QWidget通过Q_DECLARE_PRIVATE(QWidget)定义了一个 d_func() 的函数, 可以通过d_func()获取QWidgetPrivate对象的指针QWidgetPrivate中实现了QWidget中所有的真正功能, 而QWidgetPrivate真正的实现在文件qwidget_p.h中
现在剩下的就是理解 d_ptr 是在哪里初始化的,还是以 QWidget 为例
先简单看一次 QWidget 的源码,这里只保留了继承关系和构造,析构函数
qwidget.h 文件
class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
{
Q_OBJECT
Q_DECLARE_PRIVATE(QWidget)
explicit QWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
~QWidget();
}
qwidget.cpp 文件
QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
: QObject(*new QWidgetPrivate, 0), QPaintDevice()
{
Q_D(QWidget);
QT_TRY {
d->init(parent, f);
} QT_CATCH(...) {
QWidgetExceptionCleaner::cleanup(this, d_func());
QT_RETHROW;
}
}
QWidget::~QWidget()
{
Q_D(QWidget);
...
// All this widget's children are deleted first. The application exits if this widget is the main widget.
}
这里可以看到 QWidget 构造函数中 new 了一个 QWidgetPrivate 指针,创建了一个 QWidgetPrivate 传递给父类 QObject, 接下来需要看一下 QObject 中的实现
qobject.h 文件
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QObject)
public:
Q_INVOKABLE explicit QObject(QObject *parent=Q_NULLPTR);
virtual ~QObject();
protected:
QObject(QObjectPrivate &dd, QObject *parent = Q_NULLPTR);
protected:
QScopedPointer<QObjectData> d_ptr;
}
qobject.cpp 文件
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
Q_D(QObject);
d_ptr->q_ptr = this;
...
}
QObject::~QObject()
{
Q_D(QObject);
...
}
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_D(QObject);
d_ptr->q_ptr = this;
...
}
看完这段省略的代码可以看出,d_ptr 实际上就是一个 QScopedPointer<QObjectData> 的智能指针,也就是一个指向 QObjectData 的智能指针
简单点说就是
QWidget在构造的时候生成的QWidgetPrivate指针最终被写到基类QObject的d_ptr中- 这样就可以在声明
Q_DECLARE_PRIVATE(QWidget)之后,通过Q_D(QWidget)返回QWidgetPrivate指针,通过指针,对数据直接进行修改。 - 这里使用的
d_ptr实际类型是QObjectData的智能指针,指针指向QWidgetPrivate指针指向的地址, 所有通过reinterpret_cast可以直接拿到QWidgetPrivate指针
理解Q指针
理解完D指针,Q指针理解起来会很简单,首先看一下 QObjectData 的结构体以及 QObject 的构造
class Q_CORE_EXPORT QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint unused : 25;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
};
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_D(QObject);
d_ptr->q_ptr = this;
...
}
还是以 QWidget 为例,QWidget 构造的时候, 将生成的 QWidgetPrivate 指针最终写到基类 QObject 的 d_ptr 中, 同时为 d_ptr 中的指针 q_ptr 赋值 d_ptr->q_ptr = this,这里的 q_ptr 其实就是 Q指针。
这里 q_ptr 被赋予 QObject 的地址,也就是父类的地址,通过 static_cast 转换之后就能拿到 QWidget 指针了, 这里 QT 提供一个宏:如下
#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;
#define Q_Q(Class) Class * const q = q_func()
简单点说:
- 通过
Q_D(QWidget),QWidget类中可以拿到QWidgetPrivate指针 - 通过
Q_Q(QWidget),QWidgetPrivate类中可以拿到QWidget指针
总结
QT 通过 D指针,Q指针 的形式,很好的将它的实现封装了起来,对外仅仅暴露需要被调用的接口,对内将数据和接口拆开
以 QWidget为例, QWidgetPrivate 也就是 d_ptr 相当于一个数据类,其中有所有的数据信息,而 QWidget 更倾向于一个对外的接口或者说View类,当 QWidgetPrivate 需要使用到一些 QWidget 的数据的时候,可以通过 q_func() 获取 QWidget 指针, 相反 QWidget 可以通过 d_func() 获取 QWidgetPrivate 指针,很大程度上减少了二者之间的耦合