对象树管理

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

@

设置父对象的作用

众所周知,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. linux基础知识面试题

    Linux 开机启动过程 主机加电自检,加载 BIOS 硬件信息. 读取 MBR 的引导文件(GRUB.LILO). 引导 Linux 内核. 运行第一个进程 init (进程号永远为 1 ). 进入 ...

  2. Windows,easygui 安装

    在官网下载了easygui,但是根据网上的方法解压后将 easygui 文件夹(创建文件:easygui,只放easygui.py)放到Python36\Lib\site-packages下不行,有模 ...

  3. vue项目安装淘宝镜像一直失败,解决办法

    这个问题纠缠了我好几天,刚开始把npm,node卸载了重装,发现还是一样的问题,最后发现是权限不够,下面这个方法完美解决 失败原因:没有用管理员权限执行, 解决办法:找到安装nodejs安装的路径,再 ...

  4. VOIP(SIP)呼叫环境及流程试验

    宿主机:win11  IP: .1         PHONE: 102 虚拟机: v11     IP: .129     SIP SERVER 虚拟机: v10     IP: .128      ...

  5. Reincarnation

    HDU4622 Now you are back,and have a task to do: Given you a string s consist of lower-case English l ...

  6. 剑指offer----1.二维数组查找

    题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. ...

  7. Hihocoder 1067

    最近公共祖先二 离线算法 /**/ #include <cstdio> #include <cstring> #include <cmath> #include & ...

  8. Altium Designer在原理图中复制报错InvalidParameter解决

    Altium Designer 原理图复制出现  InvalidParameter Exception Occurred In Copy    解决方案为将下图红框中的√去掉 将红框中√去掉就点击右下 ...

  9. iframe页面加载完成为什么还是获取不到里面的dom

    iframe页面加载完成为什么还是获取不到里面的dom? 因为Iframe是跨域,跨域的情况下是无法获取到iframe里面的DOM的,即使iframe加载完成,也无法获取到里面的DOM. 有什么方法获 ...

  10. Arrays.asList()需要注意的点

    千万不要这样使用Arrays.asList ! 测试的几种情况及原因: public static void main(String[] args) { //第一种基本类型数组 int[] arr = ...