文章目录
前言
众所周知,在C11之前,C++的内存管理是非常让人头疼的,从C11之后新增了智能指针加强在开发过程中对内存泄漏的防范,而QT中又有一个独有的特性-对象树。
对于QObject来说,每个对象都可以存在多重的子父关系,因此这种关系整体来看就形成了对象树。从内存管理的角度分析,它有一个很重要的作用:当父对象析构后,挂载到其下的所有子对象将会被附带析构。基于这一特性,很大的预防了内存泄漏带来的风险。
在初期学习时,很多人总是会将对象树与基类子类关联起来,因为在析构顺序上,它和C++基类与子类的析构顺序是一致的(先析构子对象再析构父对象),但这属于两种概念,对象树是基于C++语法开发的独有特性,下面将通过源码分析对象树。
提示:以下源码基于QT5.12,下面案例可供参考
一、建立子父对象关系
QObject建立子父关系一般有两种方法:
1.构造一个对象时QObject(QObject *parent=nullptr);将父对象指针传入构造函数中
2.调用void QObject::setParent(QObject *parent);传入父对象指针
找到qobject.cpp源码,然后找到QObject(QObject *parent=nullptr)函数
QObject::QObject(QObject *parent)
: d_ptr(new QObjectPrivate)
{
Q_D(QObject);
d_ptr->q_ptr = this;
d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
d->threadData->ref();
if (parent) {
QT_TRY {
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData))
parent = 0;
setParent(parent);
} QT_CATCH(...) {
d->threadData->deref();
QT_RETHROW;
}
}
#if QT_VERSION < 0x60000
qt_addObject(this);
#endif
if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
Q_TRACE(QObject_ctor, this);
}
通过简单分析即可发现,当parent参数不为空时,将对其进行线程校验(是否在同一线程创建),之后调用setParent,因此有所总结:
1.两种设置父对象的方法都是调用setParent实现的。
2.在实际开发中,调用setParent需要注意对象是否在同一线程中创建,否则会有异常情况出现。
下面再来看setParent函数的实现
void QObject::setParent(QObject *parent)
{
Q_D(QObject);
Q_ASSERT(!d->isWidget);
d->setParent_helper(parent);
}
setParent本质是调用QObjectPrivate::setParent_helper(QObject *o)函数,但需要注意的是Q_ASSERT(!d->isWidget);这句话,使用 QObject::setParent() 为窗口部件设置父对象可能会导致意外行为或崩溃,因为窗口部件有自己的父子关系系统,由 Qt 窗口部件层次结构管理。窗口部件通常具有管理其几何形状和外观的父窗口部件。使用 QObject::setParent() 分配不同的父对象可能会干扰这个窗口部件层次结构并引发问题。下面再来看setParent_helper:
void QObjectPrivate::setParent_helper(QObject *o)
{
Q_Q(QObject);
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 (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 = 0;
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);
}
这块代码其实也很简单,并且注释明确简洁,总共做了3个步骤:
1.如果该对象在此之前有挂到某个父对象中,需要解除之前的关系,然后通过事件循环告知之前的父对象。
2.重新与新的父对象建立关系,由父对象中的QObjectList管理,然后通过事件循环告知新的父对象。
3.调用parentChanged,做相应的后续工作。
至此,子父对象就建立成功了。
二、对象析构
QObject::~QObject()
{
//...
if (!d->children.isEmpty())
d->deleteChildren();
//...
if (d->parent) // remove it from parent object
d->setParent_helper(0);
}
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 = 0;
isDeletingChildren = false;
}
析构函数中我对源码进行了简化,实际也分2个步骤:
1.遍历管理子对象的QObjectList并逐个删除。
2.将自身的父对象指针置空,通知父对象。
由此也解释得通对象树的析构顺序了。
总结
以上就是对Qt对象树的理解,希望能给大家带来帮助。