从源码角度理解QT特性-对象树


前言

众所周知,在C11之前,C++的内存管理是非常让人头疼的,从C11之后新增了智能指针加强在开发过程中对内存泄漏的防范,而QT中又有一个独有的特性-对象树。
对于QObject来说,每个对象都可以存在多重的子父关系,因此这种关系整体来看就形成了对象树。从内存管理的角度分析,它有一个很重要的作用:当父对象析构后,挂载到其下的所有子对象将会被附带析构。基于这一特性,很大的预防了内存泄漏带来的风险。
在初期学习时,很多人总是会将对象树与基类子类关联起来,因为在析构顺序上,它和C++基类与子类的析构顺序是一致的(先析构子对象再析构父对象),但这属于两种概念,对象树是基于C++语法开发的独有特性,下面将通过源码分析对象树。


提示:以下源码基于QT5.12,下面案例可供参考

一、建立子父对象关系

QObject建立子父关系一般有两种方法:
1.构造一个对象时QObject(QObject *parent=nullptr);将父对象指针传入构造函数中
2.调用void QObject::setParent(QObject *parent);传入父对象指针
找到qobject.cpp源码,然后找到QObject(QObject *parent=nullptr)函数

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();
    if (parent) {
        QT_TRY {
            if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
                parent = 0;
            setParent(parent);
        } QT_CATCH(...) {
            d->threadData->deref();
            QT_RETHROW;
        }
    }
#if QT_VERSION < 0x60000
    qt_addObject(this);
#endif
    if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
        reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
    Q_TRACE(QObject_ctor, this);
}

通过简单分析即可发现,当parent参数不为空时,将对其进行线程校验(是否在同一线程创建),之后调用setParent,因此有所总结:
1.两种设置父对象的方法都是调用setParent实现的。
2.在实际开发中,调用setParent需要注意对象是否在同一线程中创建,否则会有异常情况出现。
下面再来看setParent函数的实现

void QObject::setParent(QObject *parent)
{
    Q_D(QObject);
    Q_ASSERT(!d->isWidget);
    d->setParent_helper(parent);
}

setParent本质是调用QObjectPrivate::setParent_helper(QObject *o)函数,但需要注意的是Q_ASSERT(!d->isWidget);这句话,使用 QObject::setParent() 为窗口部件设置父对象可能会导致意外行为或崩溃,因为窗口部件有自己的父子关系系统,由 Qt 窗口部件层次结构管理。窗口部件通常具有管理其几何形状和外观的父窗口部件。使用 QObject::setParent() 分配不同的父对象可能会干扰这个窗口部件层次结构并引发问题。下面再来看setParent_helper:

void QObjectPrivate::setParent_helper(QObject *o)
{
    Q_Q(QObject);
    if (o == parent)
        return;
    if (parent) {
        QObjectPrivate *parentD = parent->d_func();
        if (parentD->isDeletingChildren && wasDeleted
            && parentD->currentChildBeingDeleted == q) {
            // don't do anything since QObjectPrivate::deleteChildren() already
            // cleared our entry in parentD->children.
        } else {
            const int index = parentD->children.indexOf(q);
            if (parentD->isDeletingChildren) {
                parentD->children[index] = 0;
            } else {
                parentD->children.removeAt(index);
                if (sendChildEvents && parentD->receiveChildEvents) {
                    QChildEvent e(QEvent::ChildRemoved, q);
                    QCoreApplication::sendEvent(parent, &e);
                }
            }
        }
    }
    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);
            }
        }
    }
    if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)
        QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}

这块代码其实也很简单,并且注释明确简洁,总共做了3个步骤:
1.如果该对象在此之前有挂到某个父对象中,需要解除之前的关系,然后通过事件循环告知之前的父对象。
2.重新与新的父对象建立关系,由父对象中的QObjectList管理,然后通过事件循环告知新的父对象。
3.调用parentChanged,做相应的后续工作。
至此,子父对象就建立成功了。

二、对象析构

QObject::~QObject()
{
	//...

    if (!d->children.isEmpty())
        d->deleteChildren();

	//...

    if (d->parent)        // remove it from parent object
        d->setParent_helper(0);
}

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;
}

析构函数中我对源码进行了简化,实际也分2个步骤:
1.遍历管理子对象的QObjectList并逐个删除。
2.将自身的父对象指针置空,通知父对象。
由此也解释得通对象树的析构顺序了。

总结

以上就是对Qt对象树的理解,希望能给大家带来帮助。

  • 43
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值