QT-D指针Q指针

在查看 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 指针最终被写到基类 QObjectd_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 指针最终写到基类 QObjectd_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 指针,很大程度上减少了二者之间的耦合