Qt源码之d指针分析(QObject,QObjectPrivate)

前言

  阅读过Qt源码的同学一定对d指针不陌生,前段时间其实写过一次关于Qt d指针的文章,但是感觉不够透彻就删除了,这次打算彻底地详细地再分析一次。

Pimpl机制

  对Pimpl机制不熟悉的先熟悉下Pimpl机制Pimpl机制。Qt的d指针其实主要还是采用了Pimpl机制。关于Pimpl机制的优点:

  1. 降低耦合
  2. 信息隐藏
  3. 降低编译依赖,提高编译速度
  4. 接口与实现分离

  其实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内存泄漏总结(包括检测工具)这篇文章中提到的。

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值