6

Qt源码阅读(三) 对象树管理 - 师从名剑山

 1 year ago
source link: https://www.cnblogs.com/codegb/p/17270627.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

对象树管理

个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃

设置父对象的作用

众所周知,Qt中,有为对象设置父对象的方法——setParent

而设置父对象的作用主要有,在父对象析构的时候,会自动去析构其子对象。如果是一个窗口对象,如果其父对象设置了样式表(Style Sheet),子对象也会继承父对象的样式

所以,这篇文章,咱们主要看一下setParent的源码以及QObject是怎么进行对象管理的。

设置父对象(setParent)

我们可以看到,setParent这个函数就是调用了QObjectPrivate类的setParent_helper这个函数。



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

所以,我们进一步分析setParent_helper这个函数

1|0完整源码



void QObjectPrivate::setParent_helper(QObject *o) { Q_Q(QObject); // 不能把自己设为父对象 Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself"); #ifdef QT_DEBUG // 检查对象树的循环 const auto checkForParentChildLoops = qScopeGuard([&](){ int depth = 0; auto p = parent; while (p) { if (++depth == CheckForParentChildLoopsWarnDepth) { qWarning("QObject %p (class: '%s', object name: '%s') may have a loop in its parent-child chain; " "this is undefined behavior", q, q->metaObject()->className(), qPrintable(q->objectName())); } p = p->parent(); } }); #endif

// 如果要设置的父对象就是当前的父对象,直接返回 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 (index < 0) { // we're probably recursing into setParent() from a ChildRemoved event, don't do anything } else 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 = nullptr; 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); }

1|0片段分析

  1. 一些先决条件的判断

    • 判断设置的父对象是否是自己

      // 不能把自己设为父对象 Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself"); /*...*/ // 如果要设置的父对象就是当前的父对象,直接返回 if (o == parent) return;
    • 判断原来的父对象是否处于正在删除子对象的过程中,并且当前对象已经被删除了,如果是,则什么都不做(有点迷惑)

      if (parentD->isDeletingChildren && wasDeleted && parentD->currentChildBeingDeleted == q) { // don't do anything since QObjectPrivate::deleteChildren() //already cleared our entry in parentD->children. }
    • 判断是不是通过从ChildRemoved事件递归到setParent()

      if (index < 0) { // we're probably recursing into setParent() from a ChildRemoved event, // don't do anything } else 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); } }
  2. 设置父对象,这里有一个限制,就是新设置的父对象,必须和当前对象在同一个线程,否则不能设置

    // 设置父对象 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 = nullptr; return; } // 父对象添加子对象,并发送事件 parent->d_func()->children.append(q); if(sendChildEvents && parent->d_func()->receiveChildEvents) { if (!isWidget) { QChildEvent e(QEvent::ChildAdded, q); QCoreApplication::sendEvent(parent, &e); } } }

对象的删除

  然后就是对象的管理,也就是在父对象析构的时候,自动析构掉所有的子对象。这一个在我们使用窗口部件的时候很有用,因为一个界面可能有很多个子控件,比如按钮、label等,这时候,如果一个小窗口被关闭,我们也不需要一个一个的去析构,由Qt的对象树去进行析构就好了。



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

// 删除子对象 if (!d->children.isEmpty()) d->deleteChildren();

#if QT_VERSION < 0x60000 qt_removeObject(this); #endif if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject])) reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);

Q_TRACE(QObject_dtor, this);

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

将所有的子对象进行删除,遍历容器,按照子对象所加入进来的顺序进行析构



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 = nullptr; isDeletingChildren = false; }

夹带私货时间

在使用Qt的对象树这个功能的时候,可能会遇到一种问题,会导致程序崩溃:就是手动的管理(也就是直接delete)一个有父对象的QObject,为什么会出现这样的情况呢,因为,你在delete子对象之后,并没有把这个对象从父对象的对象树里移除。在父对象进行析构的时候,还是会去遍历子对象容器,一个一个析构。这个时候,就会出现,一个对象指针被删除了两次,自然就会崩溃。

那么,如果非要自己管理这个对象,有什么办法呢?我们从对象树下手,有两种办法:

  1. 使用deleteLater

    就是调用QObject对象的deleteLater函数,来实现删除。关于deleteLater的分析,可以看这个大佬的文章Qt 中 deleteLater() 函数的使用

    QObject *object = new QObject(); QObject *m_child = new QObject(object);

    // 需要手动删除的时候 m_child->deleteLater();

  2. 先将父对象设置为空,再直接delete

    QObject *object = new QObject(); QObject *m_child = new QObject(object);

    // 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr;

  3. 先将父对象设置为空,再直接delete

    QObject *object = new QObject(); QObject *m_child = new QObject(object);

    // 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr;

个人建议使用第一种方法,也就是调用deleteLater

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK