【Qt】Qt源码学习(一):Q_D和Q_Q,简称d指针

2 篇文章 0 订阅

参考博客:
Qt Creator 源码学习 07:D 指针
通过QtCreator源码学习Qt(十二):Q_D和Q_Q指针

Q_D和Q_Q指针(d指针)简介

Qt的d指针主要的采用了Pimpl机制。
(不熟悉Pimpl机制可先了解一下,链接: Pimpl机制.)

用Qt文档中对于d指针的描述是这样的:

The Q_D and Q_Q macros are part of a design pattern called the d-pointer (also called the opaque pointer) where the implementation details of a library may be hidden from its users and changes to the implementation can be made to a library without breaking binary compatibility.

  意思是:Q_D和Q_Q宏是d指针设计模式的组成部分。在这种模式中,库的实现细节可对用户隐藏,并且可以在不破坏二进制兼容的情况下对库进行修改。

二进制兼容,在KDE WiKi上面这样描述:

A library is binary compatible, if a program linked dynamically to a former version of the library continues running with newer versions of the library without the need to recompile.
一个动态链接到较早版本的库的程序,在不经过重新编译情况下能够继续运行新版本的库,这样的库即“二进制兼容”。

  就是说,在升级库文件时,不必重新编译使用这个库的可执行文件或使用这个库的其他库文件,程序功能不会被破坏。如果库A升级了,但没做到二进制兼容,那么所有依赖它的程序需要重新编译,否则会出现各种问题。
  Pimpl机制的优点,可以实现二进制兼容。对Qt这么大的库来说,版本迭代如果每次都要重新编译代价会很大,所以Qt中大量地使用了d指针。

源码分析(以QObject类为例)

在class中 Q_DECLARE_PRIVATE 和 Q_D 配合使用,方便获取d指针,d指针指向Class##Private;
在Class##Private中Q_DECLARE_PUBLIC 和 Q_Q配合使用 ,方便获取q指针,q指针指向原class本身;

  1. 以QObject类为例,先说说 Q_DECLARE_PRIVATE 和 Q_D 。

  先看下Q_DECLARE_PRIVATE 这个宏:

#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
    friend class Class##Private;

  那么对于QObject类这个宏Q_DECLARE_PRIVATE(QObject)就是:

    inline QObjectPrivate* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));) } \
    inline const QObjectPrivate* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr));) } \
    friend class QObjectPrivate;

  其中qGetPtrHelper(T *ptr):

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

  d_func()中的d_ptr:

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

  而QObjectData,是用来保存标注位,当前类的实例,父类和子类们等:

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 deleteLaterCalled : 1;
    uint unused : 24;
    int postedEvents;
    QDynamicMetaObjectData *metaObject;
    QMetaObject *dynamicMetaObject() const;
};

  在protected中的d_ptr是一个QObjectData的智能指针,它可以被QObject的所有子类访问。d_func这个函数会把d_ptr强制转换成当前类的Private指针类型,并返回该指针。接下来看看QObject的构造函数:

QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    .......
}

  出现了d指针,它从哪来的?先看看宏Q_D:

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

  原来d指针是通过宏Q_D获得,也就是调用d_func()函数获得的返回值。前面已分析过,宏Q_DECLARE_PRIVATE,已将QObjectData指针转换成当前类的Private指针类型。
  到这里,我们可以看出Qt是采用了Pimpl机制,QObject类的具体实现放在一个Private实现类中,头文件通过宏Q_DECLARE_PRIVATE将d_ptr转换为当前类的Private类指针,然后在cpp文件中通过宏Q_D来获取这个d指针。

  1. 接着看看Q_DECLARE_PUBLIC 和 Q_Q

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

  宏Q_Q:

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

  和前面分析Q_DECLARE_PRIVATE、Q_D一样。Private实现类在头文件用宏Q_DECLARE_PRIVATE将q_ptr转换成主类指针,在cpp文件用宏Q_Q获取这个q指针。

扩展:子类的Private指针如何传递给d_ptr?(以QLineEdit类为例)

从QLineEdit构造函数开始,看看实例化一个LineEdit发生了什么:

QLineEdit::QLineEdit(QWidget* parent)
    : QLineEdit(QString(), parent)
{
}
QLineEdit::QLineEdit(const QString& contents, QWidget* parent)
    : QWidget(*new QLineEditPrivate, parent, 0)
{
    Q_D(QLineEdit);
    d->init(contents);
}

在QLineEdit构造函数中,调用了父类QWidget的一个protected权限构造函数,接下来看看这个QWidget构造函数:

QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f)
    : QObject(dd, 0), QPaintDevice()
{
    Q_D(QWidget);
    QT_TRY {
        d->init(parent, f);
    } QT_CATCH(...) {
        QWidgetExceptionCleaner::cleanup(this, d_func());
        QT_RETHROW;
    }
}

在这个QWidget构造函数中,又是调用了其父类QObject的protected权限构造函数,那看看这个QObject构造函数的实现:

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    .......
}

  显然,一个子类的Private类指针在构造函数中实例化后,是通过父类的protected权限构造函数传递给d_ptr。

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值