在查看 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
指针,很大程度上减少了二者之间的耦合