Qt -对象树

博客主页:【夜泉_ly
本文专栏:【暂无】
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

前言

Qt 的对象树是什么?
我认为,是 Qt 基于C++的继承
让所有继承自 QObject 类的对象,
能够以父子关系组织起来,
从而达到用父对象管理子对象生命周期,
的一种 设计模式

具体来说,
在一个父对象释放时,
它会释放它的所有子对象,
这样就能做到 自动化的内存管理

我们创建对象时把它挂到树上,
就不用每次都手动调用 delete

下面,我们还是来看看源码吧,
分别从构造和析构两个方面,
简单理解 Qt 对象树的底层逻辑。

构造

QObject::QObject

首先是 QObjectpublic 构造
只有这一个:

QObject::QObject(QObject *parent)
    : QObject(*new QObjectPrivate, parent)
{
}

作用很简单,
调用了另一个构造:

/*!
    \internal
 */
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{

这个跟 QObject 的设计有关,
Qt 把 QObject 所有的成员变量放进了 QObjectPrivate
QObject 中只保留了一个 QScopedPointer<QObjectData>

这也是一种设计模式,不过这不是今天的重点,而且设计模式我还没学多少。
所以直接贴出ai的回答:

这是一种称为"桥接模式"(Bridge Pattern)的设计模式,也常被称为"Pimpl惯用法"(Pointer to Implementation Idiom)或"不透明指针"(Opaque Pointer)。
在Qt的实现中,QObject类使用了这种模式,将其实现细节隐藏在QObjectPrivate类中,而QObject自身只保留了一个指向这些实现细节的指针(QScopedPointer)。
这种设计模式的主要优点包括:

  1. 封装实现细节:用户代码只需要与QObject的公共接口交互,而不需要了解其内部实现。
  2. 二进制兼容性:可以在不破坏二进制兼容性的情况下修改私有实现,因为公共类的内存布局不会改变。
  3. 减少编译依赖:使用QObject的代码不需要包含QObjectPrivate的完整定义,从而减少了编译依赖,加快了编译速度。
  4. 降低耦合度:将接口与实现分离,使系统更加灵活。
    在Qt框架中,这种模式被广泛使用,不仅仅是在QObject中,还在许多其他类中也采用了这种设计方式。

关于这个构造函数的实现,
我保留了关键的几句(我认为的):

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");

    if (parent) {
        setParent(parent);
    }
}

首先断言自己的父对象不能是自己,
然后调用 setParent 函数。

setParent 函数简单做了两句检查,
接着调用 QObjectPrivate::setParent_helper

QObjectPrivate::setParent_helper

这是对象树中的核心实现了:

void QObjectPrivate::setParent_helper(QObject *o)
{
	if (o == parent) // 避免重复设置
	    return;
	
	if (parent) {
	    QObjectPrivate *parentD = parent->d_func();
	    // 处理各种特殊情况...
	    
	    // 从原父对象的children列表中移除:
	    parentD->children.removeAt(index);
	    
	    // 发送子对象移除事件...
	}
	
	// 下面开始重新设定父对象
	parent = o;
	
	if (parent) {
	    // object hierarchies are constrained to a single thread
	    // ---翻译: 对象层次结构被约束到单个线程
	    if (threadData.loadRelaxed() != parent->d_func()->threadData.loadRelaxed()) {
	        qWarning("QObject::setParent: Cannot set parent, new parent is in a different thread");
	        parent = nullptr;
	        return;
	    }
	    
	    // 添加到新父对象的children列表:
	    parent->d_func()->children.append(q);
	    
	    // 发送子对象添加事件...
	}
}

从这里我们可以看见,

  1. 同一对象树的所有对象都必须在同一线程中,
    比如下面这种写法就是错误的:
    在这里插入图片描述
    创建 ThreadTest 对象并 start
    Qt 会报错(但不会终止程序):
    在这里插入图片描述
    下面这种写法也是错的:
    在这里插入图片描述
    Qt 会报错(但不会终止程序):
    在这里插入图片描述

  2. Qt 在每一步都对空指针进行了检查,
    因此,如果你想解除父子对象的关系,
    可以放心的 setParent(nullptr)

  3. 对于每个继承自 QObject 的对象,
    都会存在一个 QList<QObject*>children 用于存储子对象。
    (当然,严格讲,这个链表是 QObjectData 里的)。

析构

下面是 ~QObject 的核心(我认为的):

QObject::~QObject()
{
    d->wasDeleted = true;

    // 绑定数据清理:
    if (!d->bindingStorage.isValid()) {
        // 处理线程移动后的未完成绑定...
    }
    d->clearBindingStorage();

    // 处理智能指针(Qt的)引用计数...

    // 发送销毁信号:
    if (!d->wasWidget && d->isSignalConnected(0)) {
        emit destroyed(this);
    }

    // 处理信号和槽连接:
    QObjectPrivate::ConnectionData *cd = d->connections.loadAcquire();
    if (cd) {
        if (cd->currentSender) {
            // 处理当前发送者的连接...
        }

        // disconnect all receivers
        for (int signal = -1; signal < receiverCount; ++signal) {
            // 处理接收者的连接...
        }

        // Disconnect all senders:
        while (QObjectPrivate::Connection *node = cd->senders) {
            // 处理发送者的连接...
        }
    }

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

    // 从父对象中移除:
    if (d->parent)
        d->setParent_helper(nullptr);
}

可以看见,
如果我们手动 delete
由于最后一句的 setParent_helper(nullptr)
对象也会从对象树上移除
不会被父对象重复 delete

deleteChildren 就只是很单纯的遍历然后 delete

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.size(); ++i) {
        currentChildBeingDeleted = children.at(i);
        children[i] = nullptr;
        delete currentChildBeingDeleted;
    }
    children.clear();
    currentChildBeingDeleted = nullptr;
    isDeletingChildren = false;
}

提醒

虽然 Qt 做了非常多的检查,
但我们还是最好不要乱搞。

比如这种循环引用代码:

QObject a, b;
a.setParent(&b);
b.setParent(&a);

在这里插入图片描述
再比如这种不分主次的手动delete:

QObject* obj_a = new QObject(this);
QObject* obj_b = new QObject(obj_a);
delete obj_a;
delete obj_b;

在这里插入图片描述

再比如没搞清生命周期情况下的,
智能指针和对象树的混用:

QObject* obj_a = new QObject(this);
std::shared_ptr<QObject> obj_b(new QObject(obj_a));
delete obj_a;

在这里插入图片描述

注:
并不是说不能手动 delete,或者不能智能指针和对象树混用,
而是你在用的时候,要能搞清楚你在干什么。

反正我是不会用的,就让对象树帮我自动销毁多好。

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值