当前位置: 首页 > article >正文

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

对象树管理

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

文章目录

  • 对象树管理
    • 设置父对象的作用
    • 设置父对象(setParent)
      • 完整源码
      • 片段分析
    • 对象的删除
    • 夹带私货时间

设置父对象的作用

众所周知,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<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


http://www.kler.cn/a/6516.html

相关文章:

  • iterm2 focus时灰色蒙层出现的解决办法
  • <项目代码>YOLO Visdrone航拍目标识别<目标检测>
  • tslib(触摸屏输入设备的轻量级库)的学习、编译及测试记录
  • Oracle:数据库的顶尖认证
  • ScottPlot学习的常用笔记-02
  • 全脐点曲面当且仅当平面或者球面的一部分
  • java程序中常见的漏洞类型
  • 五 MySQL 存储过程
  • [jetson]pillow 报ImportError: The _imagingft C module is not installed
  • Unity游戏逆向及破解方法介绍
  • BUUCTF-逆向[GXYCTF2019]luck_guy1题解
  • day14—编程题
  • JVM垃圾回收——ZGC垃圾收集器
  • 【算法】树状数组维护总结
  • 计算机底层:循环冗余校验码CRC
  • 实验十八、测量运放的开环差模放大倍数
  • 实验二 配置Trunk和链路汇聚
  • 近世代数 笔记与题型连载 第八章(置换群)
  • Linux之进程替换
  • Octree(八叉树)
  • 【进阶C语言】指针和数组笔试题解析
  • 愚人节,聊聊那些正在坑人的“新型AI”
  • 实验5 数字图像基础
  • python函数
  • 【Python入门第四十六天】Python丨NumPy 数组重塑
  • 关于分布式系统缓存扩容的一些总结(面试装逼能用实际也很有用)