参考博客:
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本身;
- 以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指针。
- 接着看看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。