Qt中的d指针和q指针

概述

如果程序从一个以前版本的库动态链接到新版本的库之后,能够继续正常运行,而不需要重新编译,那么我们就说这个库是二进制兼容的。

如果一个程序需要重新编译来运行一个新版本的库,但是不需要对程序的源代码进一步的修改,这个库就是源代码兼容的。

如果不确保版本之间的二进制兼容性,人们将被迫提供静态链接的二进制文件。静态二进制文件不好,因为它们浪费资源(尤其是内存),不能让程序从库中错误修正或扩展中受益。

要保证二进制兼容性,就得尽量让类不包含私有变量,Qt的风格就保证了这一点,不信可以看QObject的源码,你会惊讶地发现它竟然只有一个成员变量QScopedPointer<QObjectData> d_ptr;。QObject的大小是8,除了虚函数表指针需要的4个字节以外,另外的4个字节就是这个d_ptrQObjectData是包含所有数据的结构体,这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类即QObject,d_ptr就叫做d指针。

这种设计模式叫做句柄实体模式,也就是以QObject为基类的类一般都是句柄类,一般只有一个指针指向一个实体类,在实体类中保存全部的数据。QObject比平常的用法更复杂,涉及到QObjectDataQObjectPrivate两个类,一般只要有ClassPrivate就够了。

源码分析

相关几个类源码大致如下:

class Q_CORE_EXPORT QObject
{
    Q_DECLARE_PRIVATE(QObject)
    ......
    protected:
        QScopedPointer<QObjectData> d_ptr;
}

class Q_CORE_EXPORT QObjectData {
public:
    virtual ~QObjectData() = 0;
    QObject *q_ptr;
    ......  
}

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
    ......
}

d指针的优点:
1. 隐藏实现细节
2. 头文件中没有任何实现细节,可以作为API使用。
3. 由于原本在头文件的实现部分转移到了源文件,所以编译速度有所提高。
4. 实现二进制兼容性,避免重新编译

Q_DECLARE_PRIVATE和Q_DECLARE_PUBLIC

Q_DECLARE_PRIVATE定义:

#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 的d_func()函数,返回值分别是我们的d_ptr指针和const指针,又把ClassPrivate声明为Class的友元。

Q_DECLARE_PUBLIC定义:

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

与之前的宏相对,返回的是指向class的指针,因为我们可能在ClassPrivate的实现里访问Class的一些东西。已经让主类通过d_func()访问ClassPrivate的数据,反过来让ClassPrivate访问主类的数据用的就是q_func()

Q_Q和Q_D

如果看源码实现部分,你会发现很少看到d_ptrq_ptr,更多的是dq。如果按住Ctrl和鼠标左键跟踪这两个变量,发现不能跳转。奇怪,这两个东西是哪来的?就让我们来看看一部分源码:

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    ......
        if (d->isWidget) {
        if (parent) {
            d->parent = parent;

void QObjectPrivate::setParent_helper(QObject *o)
{
    Q_Q(QObject);

突然就蹦出了一个d,查看头文件都没有发现,前置声明也没有,答案肯定在Q_D这个宏里了,它和Q_Q的定义:

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

明显是呼应前面的两个宏,对指针访问进行了简化,以后代码中使用d和q就可以了。

不能把ClassPrivate的头文件和Class头文件放到一起,常见做法是:定义一个ClassPrivate的头文件,例如使用myclass_p.h(这也是 Qt 的命名方式)。并且记住,不要把myclass_p.h放到发布的include下面!在myclass.h中,使用前向声明Class MyClassPrivate

我在Github上写的示例程序:演示Qt中的d指针和q指针如何使用

参考:
QObject 源代码阅读
Qt之美:D指针/私有实现
Qt之美:d指针/p指针详解

猜你喜欢

转载自blog.csdn.net/yao5hed/article/details/81075765