最近开始研究QT源码,鉴于群友说把知识点分享出来,所以决定分享出来,文笔不是很好,勿喷。未来的计划是会每周发一篇关于源码分析的文章,持续到2021年。这也是我2021年的学习计划之一。
进入正题
Q_D指针在QT源码中大量的使用,其根本目的在于解决二进制兼容问题。
何为二进制兼容?
1.动态链接到库的前一个版本的程序继续与库的新版本一起运行而不需要重新编译,则库是二进制兼容的。
2.程序需要重新编译以使用新版本的库运行,但不需要任何进一步的修改,则该库是源代码兼容的。
要能够实现二进制兼容,就要求每一个结构以及每一个对象的数据模型保持不变。所谓“数据模型保持不变”,就是不能在类中增加、删除数据成员。这是 C++ 编译器要求的,其根本是保持数据成员在数据模型中的位移保持不变。那么如何实现?很简单,我们让所有的共有类都拥有一个指针,这个指针包含所有数据的私有数据结构。且这个私有的数据结构可在任意版本中修改,从而不影响该类的使用,因为这个指针是私有的,外部调用只能看到一个不变化的共有类,该共有类只拥有一个指针,这个指针就是称为d指针的Q_D指针。
首先看源码内Q_D的声明,源码部分只截取了关键的部分:
qobject.h
class Q_CORE_EXPORT QObjectData {
Q_DISABLE_COPY(QObjectData)
public:
QObjectData() = default;
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
......
};
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
Q_DECLARE_PRIVATE(QObject)
public:
Q_INVOKABLE explicit QObject(QObject *parent=nullptr);
virtual ~QObject();
......
protected:
QObject(QObjectPrivate &dd, QObject *parent = nullptr);
protected:
QScopedPointer<QObjectData> d_ptr;
}
public:
Q_INVOKABLE explicit QObject(QObject *parent=nullptr);
virtual ~QObject();
紧接着再看看QScopedPointer
QScopedPointer Classdoc.qt.io/qt-5/qscopedpointer.html
查阅文档可知这是QT自己提供的 smart pointer。智能指针的作用相信学过C++的都知道是什么。接着往下看这个是在什么时候进行的赋值
qobject.cpp
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");
Q_D(QObject);
d_ptr->q_ptr = this;
.......
}
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");
Q_D(QObject);
d_ptr->q_ptr = this;
.......
}
我们再看看Q_D()这个宏
qglobal.h
#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;
#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;
预处理器会把##前后进行强制连接,我们把这个宏展开,就会得到以下函数:
inline QObjectPrivate *d_func()
{
return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));
}
inline const Class##Private* d_func() const
{
Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));
}
再看看qGetPtrHelper这个函数的定义
qglobal.h
template <typename T> inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Ptr> inline auto qGetPtrHelper(Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }
从代码得知,定义了一个qobjec.cpp中定义了一个QObject的常量指针,保存了d_fun()的返回值也就得d_ptr。
注意我们再回头看看q_prt的声明
class Q_CORE_EXPORT QObjectData {
Q_DISABLE_COPY(QObjectData)
public:
QObjectData() = default;
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
......
};
qptr在QObjectData里声明为public。如何访问q_prt也就不言而喻了。