物件樹管理
個人經驗總結,如有錯誤或遺漏,歡迎各位大佬指正 ??
@
目錄- 物件樹管理
- 設定父物件的作用
- 設定父物件(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 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);
}
片段分析
-
一些先決條件的判斷
-
判斷設定的父物件是否是自己
// 不能把自己設為父物件 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 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子物件之后,并沒有把這個物件從父物件的物件樹里移除,在父物件進行析構的時候,還是會去遍歷子物件容器,一個一個析構,這個時候,就會出現,一個物件指標被洗掉了兩次,自然就會崩潰,
那么,如果非要自己管理這個物件,有什么辦法呢?我們從物件樹下手,有兩種辦法:
-
使用
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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/548592.html
標籤:其他
下一篇:2023年找作業的心酸歷程
