前言
阅读过Qt源码的同学一定对d指针不陌生,前段时间其实写过一次关于Qt d指针的文章,但是感觉不够透彻就删除了,这次打算彻底地详细地再分析一次。
Pimpl机制
对Pimpl机制不熟悉的先熟悉下Pimpl机制Pimpl机制。Qt的d指针其实主要还是采用了Pimpl机制。关于Pimpl机制的优点:
- 降低耦合
- 信息隐藏
- 降低编译依赖,提高编译速度
- 接口与实现分离
其实Pimpl机制最大作用是实现二进制兼容,所谓“二进制兼容性”指的就是在升级(也可能是 bug fix)库文件的时候,不必重新编译使用这个库的可执行文件或使用这个库的其他库文件,程序的功能不被破坏。如果库A升级没有能够做到二进制兼容,那么所有依赖它的程序(或库)都需要重新编译,否则会出现各种未知异常,其直接现象就是程序莫名其妙地挂掉。想想Qt这么大的库,版本这么多如果每次都要重新编译那么代价是很大的,当然Pimpl也是有缺点的,多了一个指针,内存肯定会增大。
源码分析
先以QObject类为例进行分析,以QObject为基类的所有qt的类都会调用Q_DECLARE_PRIVATE这个宏
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
Q_DECLARE_PRIVATE(QObject)
...
}
来看下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;
那么Q_DECLARE_PRIVATE(QObject)展开就是:
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
friend class QObjectPrivate;
也就是:
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
inline QObjectPrivate* d_func() { return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
inline const QObjectPrivate* d_func() const { return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr)); } \
friend class QObjectPrivate;
...
}
qGetPtrHelper:
template <typename T> static 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;
};
可以看到d_ptr是一个QObjectData的智能指针,因为是protected权限的,所以QObject的所有子类都能访问它,d_func函数把它强制转换成当前类的QObjectPrivate指针类型(QObjectPrivate继承于QObjectData),并返回该指针,这样我们在每个QObject的子类中使用该宏就能完成转换。那么d_func这个函数在什么时候调用呢?接着看qobject.cpp文件的一个构造函数:
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()
Q_D(QObject); 即:
QObjectPrivate * const d = d_func();
到这里很明白了,d指针原来是通过宏Q_D获得的,也就是函数d_func的返回值,上文我们分析过了,d_func通过Q_DECLARE_PRIVATE宏来获得,将QObjectData指针转换成当前类的QObjectPrivate指针类型,并返回该指针。
来看一个例子,QObject的setObjectName函数:
/*
Sets the object's name to \a name.
*/
void QObject::setObjectName(const QString &name)
{
Q_D(QObject);
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
if (d->extraData->objectName != name) {
d->extraData->objectName = name;
emit objectNameChanged(d->extraData->objectName, QPrivateSignal());
}
}
先简单总结下,Qt采用了Pimpl机制,也就是私有实现,以QObject为基类的类的具体实现放在一个XXXPrivate中,头文件中通过宏Q_DECLARE_PRIVATE来返回将d_ptr转换为当前类的XXXPrivate的一个指针,然后cpp中通过宏Q_D来获取这个d指针。到这里你可能会有一个疑问了,QObject的子类的XXXPrivate的指针是如何传递给d_ptr的? 带着这个问题我们往下看。
首先我们先看一个QPushButton的例子,将Widget设为QPushButton的parent:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
...
QPushButton *button = new QPushButton(this);
...
}
我们依次查看构造函数看看发生了什么:
QPushButton,调用了QAbstractButton的一个protected权限的构造函数:
QPushButton::QPushButton(QWidget *parent)
: QAbstractButton(*new QPushButtonPrivate, parent)
{
Q_D(QPushButton);
d->init();
}
QAbstractButton,又调用了QWidget的一个proteced权限的构造函数:
/*! \internal
*/
QAbstractButton::QAbstractButton(QAbstractButtonPrivate &dd, QWidget *parent)
: QWidget(dd, parent, 0)
{
Q_D(QAbstractButton);
d->init();
}
QWidget,又调用了QObject的一个proteced权限的构造函数:
/*! \internal
*/
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;
}
}
QObject 的proteced权限的构造函数,可以看到QPushButton构造函数中的*new QPushButtonPrivate,最终传递给了d_ptr。
/*!
\internal
*/
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
...
}
一张图方便理解:
拓展
qt中以QObject基类的类的构造函数都会指定一个parent的参数,那么这个参数有什么用呢?比如上文中QPushButton,指定了this也就是Widget类为parent,在一次次调用父类构造函数时会调用到QWidget的一个构造函数:
/*! \internal
*/
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;
}
}
参数中parent也就是new QPushButton(this) 时指定为parent的this指针。接着会调用 d->init(parent, f) 这个函数:
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
Q_Q(QWidget);
if (Q_UNLIKELY(!qobject_cast<QApplication *>(QCoreApplication::instance())))
qFatal("QWidget: Cannot create a QWidget without QApplication");
Q_ASSERT(allWidgets);
if (allWidgets)
allWidgets->insert(q);
...
}
又出现一个Q_Q宏,经过类似的分析发现就是定义了一个q指针指向了d_ptr指针的q_ptr成员;allWidgets时QWidgetPrivate的一个静态成员,保存了所有的以QWidget为基类的指针。这个q指向的q_ptr在最终调用QObject构造函数时初始化的:
QObject::QObject(QObjectPrivate &dd, QObject *parent)
: d_ptr(&dd)
{
Q_D(QObject);
d_ptr->q_ptr = this;
...
}
继续看 void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) 这个函数:
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
{
...
else if (parentWidget)
q->setParent(parentWidget, data.window_flags);
...
}
调用的void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)这个函数:
void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
...
d->setParent_sys(parent, f);
...
}
调用的时void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) 这个函数:
void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
{
...
QObjectPrivate::setParent_helper(newparent);
...
}
调用的void QObjectPrivate::setParent_helper(QObject *o) 这个函数:
void QObjectPrivate::setParent_helper(QObject *o)
{
...
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData != parent->d_func()->threadData) {
qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");
parent = 0;
return;
}
parent->d_func()->children.append(q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent, &e);
}
}
}
...
}
现在很明了了,QPushButton设置parent最终传递给了d->parent;我们还可以看到parent->d_func()->children.append(q);这行代码其实是获取到了parent的d指针,然后把当前的指针保存到了parent的d指针的children的变量中。至此我们大概了解了d指针中的parent和children是如何赋值的。
构造函数差不多了,来看下QObject的析构函数:
QObject::~QObject()
{
...
if (!d->children.isEmpty())
d->deleteChildren();
...
}
...
void QObjectPrivate::deleteChildren()
{
Q_ASSERT_X(!isDeletingChildren, "QObjectPrivate::deleteChildren()", "isDeletingChildren already set, did this function recurse?");
isDeletingChildren = true;
// delete children objects
// don't use qDeleteAll as the destructor of the child might
// delete siblings
for (int i = 0; i < children.count(); ++i) {
currentChildBeingDeleted = children.at(i);
children[i] = 0;
delete currentChildBeingDeleted;
}
children.clear();
currentChildBeingDeleted = 0;
isDeletingChildren = false;
}
看到了吗,如果一个以QObject为基类的类的实例delete的话它的所有parent都会被删除掉,所以一个类指定了parent的话就我们就不必太过关心该类的内存释放问题,这也是在Qt内存泄漏总结(包括检测工具)这篇文章中提到的。