博客主页:【夜泉_ly】
本文专栏:【暂无】
欢迎点赞👍收藏⭐关注❤️
前言
Qt 的对象树是什么?
我认为,是 Qt 基于C++的继承,
让所有继承自 QObject
类的对象,
能够以父子关系组织起来,
从而达到用父对象管理子对象生命周期,
的一种 设计模式。
具体来说,
在一个父对象释放时,
它会释放它的所有子对象,
这样就能做到 自动化的内存管理。
我们创建对象时把它挂到树上,
就不用每次都手动调用 delete
。
下面,我们还是来看看源码吧,
分别从构造和析构两个方面,
简单理解 Qt 对象树的底层逻辑。
构造
QObject::QObject
首先是 QObject
的 public
构造
只有这一个:
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)。
这种设计模式的主要优点包括:
- 封装实现细节:用户代码只需要与QObject的公共接口交互,而不需要了解其内部实现。
- 二进制兼容性:可以在不破坏二进制兼容性的情况下修改私有实现,因为公共类的内存布局不会改变。
- 减少编译依赖:使用QObject的代码不需要包含QObjectPrivate的完整定义,从而减少了编译依赖,加快了编译速度。
- 降低耦合度:将接口与实现分离,使系统更加灵活。
在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);
// 发送子对象添加事件...
}
}
从这里我们可以看见,
-
同一对象树的所有对象都必须在同一线程中,
比如下面这种写法就是错误的:
创建ThreadTest
对象并start
,
Qt 会报错(但不会终止程序):
下面这种写法也是错的:
Qt 会报错(但不会终止程序):
-
Qt 在每一步都对空指针进行了检查,
因此,如果你想解除父子对象的关系,
可以放心的setParent(nullptr)
。 -
对于每个继承自
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语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!