• Qt源码阅读(三) 对象树管理


    对象树管理

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

    @

    设置父对象的作用

    众所周知,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这个函数

    完整源码

    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. 一些先决条件的判断

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

        	// 不能把自己设为父对象 
        	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(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

  • 相关阅读:
    C //例4.10 运输公司对用户计算运输费用。路程(skm)越远,每吨·千米运费越低。标准如下:
    visual studio 启用DPI识别功能
    jQuery append和prepend和appendTo的区别和用法
    《C和指针》读书笔记(第十四章 预处理器)
    day03_顺丰快递分拣小程序
    中国地级市-环境规制18个相关指标数据(2002-2021年)
    【CVPR2022】用于域适应语义分割的域无关先验
    基于cortex-M3,M4下硬件层、驱动层的解耦软件框架设计
    C++匿名函数lambda详解
    【字符串函数内功修炼】strcpy + strcat + strcmp(一)
  • 原文地址:https://www.cnblogs.com/codegb/p/17270627.html