建设网站怎么备案怎么注册域名网址
对象树管理
个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃
文章目录
- 对象树管理
- 设置父对象的作用
- 设置父对象(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 threadif (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);
}
片段分析
-
一些先决条件的判断
-
判断设置的父对象是否是自己
// 不能把自己设为父对象 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); } }
-
-
设置父对象,这里有一个限制,就是新设置的父对象,必须和当前对象在同一个线程,否则不能设置。
// 设置父对象parent = o;if (parent) {// object hierarchies are constrained to a single threadif (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 < 0x60000qt_removeObject(this);
#endifif (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 objectd->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 siblingsfor (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子对象之后,并没有把这个对象从父对象的对象树里移除。在父对象进行析构的时候,还是会去遍历子对象容器,一个一个析构。这个时候,就会出现,一个对象指针被删除了两次,自然就会崩溃。
那么,如果非要自己管理这个对象,有什么办法呢?我们从对象树下手,有两种办法:
-
使用
deleteLater
就是调用
QObject
对象的deleteLater
函数,来实现删除。关于deleteLater
的分析,可以看这个大佬的文章Qt 中 deleteLater() 函数的使用QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->deleteLater();
-
先将父对象设置为空,再直接delete
QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr;
-
先将父对象设置为空,再直接delete
QObject *object = new QObject(); QObject *m_child = new QObject(object);// 需要手动删除的时候 m_child->setParent(nullptr); delete m_child; m_child = nullptr;
个人建议使用第一种方法,也就是调用deleteLater