QtD指针和Q指针及使用
阅读Qt的源代码的时候,我们经常看Q_D、Q_Q、Q_DECLARE_PRIVATE、Q_DECLARE_PUBLIC这几个宏,这几个宏是干什么用的呢?其实这几个宏就是实现D指针和Q指针的宏,D指针在Qt的源码中大量使用,根本目的在于解决二进制兼容问题。至于什么二进制兼容,可以查阅其他资料,这里只讲Qt中是怎么实现的。1.D指针及Q_指针
我们先建一个Test类和私有类TestPrivate类,在Test类中添加d_ptr成员,在TestPrivate中添加q_ptr成员。头文件如代码1所示:#ifndef TEST_H #define TEST_H #include class TestPrivate; class Test { Q_DECLARE_PRIVATE(Test) public: Test(); virtual ~Test(); void setValue(int v); int value() const; protected: QScopedPointer d_ptr; }; #endif // TEST_H
代码1
源文件如代码2所示:#include "test.h" class TestPrivate { Q_DECLARE_PUBLIC(Test) public: Test *q_ptr; int m_value; }; Test::Test() : d_ptr(new TestPrivate) { d_ptr->q_ptr = this; d_ptr->m_value = 10; } Test::~Test() { } void Test::setValue(int v) { Q_D(Test); if (d->m_value == v) { return; } d->m_value = v; } int Test::value() const { Q_D(const Test); return d->m_value; }
代码2
在上面代码中我们使用了Q_D、Q_DECLARE_PRIVATE、Q_DECLARE_PUBLIC宏,这个几个宏如代码3所示(参看qglobal.h)template static inline T *qGetPtrHelper(T *ptr) { return ptr; } template 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(qGetPtrHelper(d_ptr)); } inline const Class##Private* d_func() const { return reinterpret_cast(qGetPtrHelper(d_ptr)); } friend class Class##Private; #define Q_DECLARE_PUBLIC(Class) inline Class* q_func() { return static_cast(q_ptr); } inline const Class* q_func() const { return static_cast(q_ptr); } friend class Class; #define Q_D(Class) Class##Private * const d = d_func() #define Q_Q(Class) Class * const q = q_func()
代码3
然后我们可以看到展开之后的头文件如代码4所示:#ifndef TEST_H #define TEST_H #include class TestPrivate; class Test { inline TestPrivate* d_func() { return reinterpret_cast(d_ptr.data()); } inline const TestPrivate* d_func() const { return reinterpret_cast(d_ptr.data()); } friend class TestPrivate; public: Test(); virtual ~Test(); void setValue(int v); int value() const; protected: QScopedPointer d_ptr; }; #endif // TEST_H
代码4
展开之后的源文件如代码5所示:#include "test.h" class TestPrivate { inline Test* q_func() { return static_cast(q_ptr); } inline const Test* q_func() const { return static_cast(q_ptr); } friend class Test; public: Test *q_ptr; int m_value; }; Test::Test() { d_ptr.reset(new TestPrivate); d_ptr->q_ptr = this; d_ptr->m_value = 10; } Test::~Test() { } void Test::setValue(int v) { TestPrivate * const d = d_func(); if (d->m_value == v) { return; } d->m_value = v; } int Test::value() const { const TestPrivate * const d = d_func(); return d->m_value; }
代码5
展开之后我们就可以清晰地看到这几个宏做了什么。以QObject为例,在QObject的源代码中有如代码6所示,在构造函数中实现的d_ptr和q_ptr的初始化,跟上面的Test类似:// 头文件中(qobject.h) class Q_CORE_EXPORT QObject { …… protected: QObject(QObjectPrivate &dd, QObject *parent = Q_NULLPTR); protected: QScopedPointer d_ptr; …… } // 源文件中(qobject.cpp) QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate) { Q_D(QObject); d_ptr->q_ptr = this; …… }
代码6D指针和Q指针使用
假如我们的类是从Qt的类继承下来的,以QObject和上面的Test类为例,由于QObject中已经有了d_ptr成员了,而且Qt的类都会有一个类似QObject(QObjectPrivate &dd, QObject *parent = Q_NULLPTR) 这样的受保护的构造函数,现在需要做的就是Test继承QObject、TestPrivate继承QObjectPrivate、在Test的构造函数中调用QObject(QObjectPrivate &dd, QObject *parent = Q_NULLPTR) 这种构造函数。需要注意的是QObjectPrivate是在qobject_p.h头文件中,如果要包含这个头文件,我们需要在pro中添加QT += core-private,然后在代码中添加#include。
修改之后的头文件如代码7所示:#ifndef TEST_H #define TEST_H #include class TestPrivate; class Test : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(Test) public: Test(QObject *parent = nullptr); virtual ~Test(); void setValue(int v); int value() const; }; #endif // TEST_H
代码7
修改之后的源文件如代码8所示,注意构造函数的变化和TestPrivate继承QObjectPrivate:#include "test.h" #include class TestPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(Test) public: int m_value; }; Test::Test(QObject *parent) : QObject (*(new TestPrivate), parent) { Q_D(Test); d->m_value = 10; } Test::~Test() { } void Test::setValue(int v) { Q_D(Test); if (d->m_value == v) { return; } d->m_value = v; } int Test::value() const { Q_D(const Test); return d->m_value; }
代码8
假如我们的类是类似最开始Test的那种,就像代码1和代码2那样实现即可,另外如果我们的d指针不是d_ptr而是像m_ptr这样该怎么办?其实Qt还有另外一个宏Q_DECLARE_PRIVATE_D, 只需要把Q_DECLARE_PRIVATE(Test)替换成Q_DECLARE_PRIVATE_D(m_ptr, Test)。
顺便讲一下Q_PRIVATE_SLOT这个宏,这个宏可以把私有类的成员函数注册共有类的槽函数。还是以Test为例,在头文件中添加代码9:private: Q_PRIVATE_SLOT(d_func(), void _q_timeout())
代码9
在源文件中TestPrivate中添加了一个m_timer和函数_q_timeout ,连接timer的timeout信号,修改之后代码如代码10所示,注意添加最后一行的#include"moc_test.cpp" ,如果不添加的话会导致链接错误:无法解析的外部符号。class TestPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(Test) public: int m_value; QTimer *m_timer; void _q_timeout(); }; Test::Test(QObject *parent) : QObject (*(new TestPrivate), parent) { Q_D(Test); d->m_value = 10; d->m_timer = new QTimer(this); connect(d->m_timer, SIGNAL(timeout()), this, SLOT(_q_timeout())); d->m_timer->start(1000); } …… void TestPrivate::_q_timeout() { qDebug() << "time out"; } #include "moc_test.cpp"
代码10
如果Test类可以让其他类继承,使用D指针的话,需要把TestPrivate的声明放到test_p.h中去,就像qobject_p.h那样,继承类就可以使用#include"test_p.h"包含TestPrivate类了。当然还需要为Test添加一个受保护的构造函数Test(TestPrivate &dd, QObject *parent = Q_NULLPTR)。 感兴趣的小伙伴可以自己实现一下。看看有没有什么问题。
好了,关于D指针和Q指针就讲到这里了,大家有什么想知道了,可以在评论区告诉我,有空的时候帮大家整理出来。