对象树管理

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

@

设置父对象的作用

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

Qt源码阅读(三) 对象树管理的更多相关文章

  1. 25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment

    25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment 25 BasicUsageEnvironment0基本使用环境基类— ...

  2. 26 BasicUsageEnvironment基本使用环境——Live555源码阅读(三)UsageEnvironment

    26 BasicUsageEnvironment基本使用环境--Live555源码阅读(三)UsageEnvironment 26 BasicUsageEnvironment基本使用环境--Live5 ...

  3. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment

    24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类——Live555源码阅读 ...

  4. SparkSQL(源码阅读三)

    额,没忍住,想完全了解sparksql,毕竟一直在用嘛,想一次性搞清楚它,所以今天再多看点好了~ 曾几何时,有一个叫做shark的东西,它改了hive的源码...突然有一天,spark Sql突然出现 ...

  5. [日常] gocron源码阅读-使用go mod管理依赖源码启动gocron

    从 Go1.11 开始,golang 官方支持了新的依赖管理工具go modgo mod download: 下载依赖的 module 到本地 cachego mod edit: 编辑 go.modg ...

  6. JDK源码阅读(三) Collection<T>接口,Iterable<T>接口

    package java.util; public interface Collection<E> extends Iterable<E> { //返回该集合中元素的数量 in ...

  7. SpringMVC源码阅读(三)

    先理一下Bean的初始化路线 org.springframework.beans.factory.support.AbstractBeanDefinitionReader public int loa ...

  8. 看懂Qt源代码-Qt源码的对象数据存储

    第一次看Qt源代码的人都会被其代码所迷惑,经常会看到代码中的d_ptr成员.d_func(函数)和Q_DECLARE_PRIVATE等奇怪的宏,总是让人一头雾水,下面这篇文章转自http://www. ...

  9. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  10. 23 使用环境 UsageEnvironment——Live555源码阅读

    23 使用环境 UsageEnvironment——Live555源码阅读(三)UsageEnvironment 23 使用环境 UsageEnvironment——Live555源码阅读(三)Usa ...

随机推荐

  1. ESP8266开发(物联网)

    使用开发板制作wifi干扰器https://www.jianshu.com/p/f064ca36ee92

  2. 类继承(c++ primer plus)课后习题

    第一题: // base class class Cd { // represents a CD disk private: char performers[50]; char label[20]; ...

  3. C++与C语言中struct 与typedef struct 应用区别(摘自csdn mpp_king)

    typedef是类型定义的意思.typedef struct 是为了使用这个结构体方便.具体区别在于:若struct node {}这样来定义结构体的话.在申请node 的变量时,需要这样写,stru ...

  4. 当前SAT主要关键技术及其相关文献2022-11-1

    摘录自: Tasniem Nasser Al-Yahya, Mohamed El Bachir Menai, Hassan Mathkour:Boosting the Performance of C ...

  5. 盒模型属性-width height-padding-border-margin

    宽度 width: 作用:设置可以添加元素内容的区域的宽度. 属性值:  特殊应用: • 如果一个元素不添加width 属性,默认属性值为auto,不同的元素浏览器会根据其 特点自动计算出实际宽度,例 ...

  6. redis和memcached区别总结

    1.数据结构:redis支持五种数据结构(字符串,列表,哈希,集合,有序集合),并支持很多相   关计算,例如排序,阻塞等等.支持阻塞队列,锁,线程通信等功能.而memcached只支持kv简单存储 ...

  7. JS数组创建

    1.使用Array构造函数 var arr1 = new Array(); //创建一个空数组 var arr2 = new Array(10); // 创建一个包含10项的数组 var arr3 = ...

  8. opengl编程天天踩的坑

    1. VBO 的 target 是 GL_ARRAY_BUFFER 不是  GL_VERTEX_BUFFER 2. glUniform()用来给uniform传变量 别用成 glProgramUnif ...

  9. EF 操作实例

    一.Linq语法 List操作 1.1          有参 public List<GoodsInfo> ShowInfo(string GName,int?Gid) { //true ...

  10. Oracle表主键作为外键都用在哪些表查询

    Oracle中,如果设置了外键,删除数据时,必须将外键关联一并删除,但是如果对项目不是很熟悉时,我们无法判断到底都在哪些表中有外键关联,以下提供了一个查询的SQL,可以通过数据库查询,查找到所有的外键 ...