Qt对象树及实现
在写Qt程序时我们会发现一个现象——我们在new一个指定了父对象的对象时我们并不会去调用delete释放回收资源,这其实就是用的对象树来实现的对子对象资源的回收而不是智能指针,我们先通过一个简单的Qt实例来具体看看,下面是我们在Qt工程中添加的一个类MyObject:
class MyObject : public QWidget
{
public:
explicit MyObject(QString na, QWidget *parent = nullptr);
~MyObject();
private:
QString m_name;
};
MyObject::MyObject(QString na, QWidget *parent) : QWidget(parent)
{
m_name = na;
qDebug() << "create MyObject " << na;
}
MyObject::~MyObject()
{
qDebug() << m_name << " ~MyObject";
}
然后我们在主窗口对象的构造函数中实例化两个MyObject对象obj1与obj2:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
MyObject *obj1 = new MyObject("obj1", this);
MyObject *obj2 = new MyObject("obj2");
}
MainWindow::~MainWindow()
{
delete ui;
}
当我们运行并结束程序时的效果如下:
从输出的结果我们可以看出只有obj1对象调用的对应的析构函数而obj2并没有,所以这与智能指针肯定是没关系的,因为如果时通过只能指针实现那obj2对象在结束后也应该会调用对应的析构函数,那这里其实就是通过Qt的对象树机制实现的,在对象树中的父对象会负责对子对象内存的管理,所以就会出现上面的结果,obj1指定了父对象this(mainwindow)而obj2并没有,所以当父对象mainwindow释放时就会将其子对象obj1的内存一并回收。
在Qt中对象树就承载了对象及对象内存管理的作用,管理对象时可以方便我们对对象树上的任意一节点对象进行访问(比如窗口界面上我们可以用Tab键去切换窗口上选中的对象)结束时也可以方便对对象树节点上的对象内存进行自动回收,简化编程工作及减小内存泄漏风险(如果没有对象树的管理机制,我们每new一个对象就必须有一个对应的delete来防止内存泄漏,这样一是写起来会有大量delete操作,二是容易出现写漏delete导致内存泄漏),所以对象树机制在Qt中是非常重要的一个对象管理手段。
Qt中提供了以下接口方便我们对对象树进行操作及访问:
//打印当前对象的对象树信息
void dumpObjectInfo() const
//打印当前对象的对象树
void dumpObjectTree() const
//获取对象的所有子对象
const QObjectList & children() const
//获取当前对象的父对象指针
QObject * parent() const
//为当前对象设置父对象节点
void setParent(QObject *parent)
//获取对象名称
QString objectName() const
//设置对象名称
void setObjectName(const QString &name)
//根据子对象名称查找子对象,默认返回直接节点的那一个子对象,FindChildOptions值对应的是查找规则FindChildrenRecursively表示递归的查找所有子节点直到找到对应子节点或递归遍历完所有子节点,FindDirectChildrenOnly表示只在直接子节点中查找,若查找的有多个应该选择QList<T> findChildren(),参数与findChild一样
T findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
要想更清楚的了解对象树的工作原理我们可以参考Qt对象树的使用自己实现一个对象树,对象树的实现主要考虑两点:一是如何自动构建一棵对象树,二是如何通过父对象自动去析构所有子对象;其实这两个的实现都是通过在父对象中维护一个直接子对象的容器,构建树就是向对应的容器中添加对象节点,释放就是利用父对象析构时去释放其维护的对象容器中的子对象,通过析构自动递归完成整个对象树节点内存的回收,下面时参考上面Qt中QObject类的对象树接口用C++模拟对象树机制的实现:
myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <list>
#include <string>
class MyObject
{
public:
explicit MyObject(MyObject *parent = nullptr);
virtual ~MyObject();
std::string ObjectName() const;
void setObjectName(const std::string &name);
const std::list<MyObject *> children();
MyObject *parent() const;
void setParent(MyObject *parent);
void dumpObjectTree() const;
protected:
std::list<MyObject *> m_childList; //保存直接子对象指针的list容器
std::string m_objName; //存储对象名称
MyObject *m_parent; //指向当前对象父对象的指针
void addChild(MyObject *child);
void removeChild(MyObject *child);
};
#endif // MYOBJECT_H
myobject.cpp
#include "myobject.h"
#include <iostream>
MyObject::MyObject(MyObject *parent)
{
setParent(parent);
}
MyObject::~MyObject()
{
std::cout << "~MyObject " << ObjectName() << std::endl;
if(m_parent != nullptr) { //当子节点先于父节点释放时,需从父节点中移除当前子节点
m_parent->removeChild(this);
}
//释放所有直接子节点对象的内存
for(MyObject* &obj : m_childList) {
obj->setParent(nullptr); //将要移除的子节点的父节点置空
delete obj;
obj = nullptr;
}
m_childList.clear(); //最后清空当前节点的子节点
}
//获取对象名称
std::string MyObject::ObjectName() const
{
return m_objName;
}
//设置对象名称
void MyObject::setObjectName(const std::string &name)
{
m_objName = name;
}
//获取所有直接子对象节点
const std::list<MyObject *> MyObject::children()
{
return m_childList;
}
//获取当前对象的父对象
MyObject *MyObject::parent() const
{
return m_parent;
}
//设置当前对象的父对象
void MyObject::setParent(MyObject *parent)
{
m_parent = parent;
if(nullptr == m_parent) return;
m_parent->addChild(this);
}
//打印对象树节点的对象名称
void MyObject::dumpObjectTree() const
{
static std::string tab = "";
static std::string tmp = "";
//tab与tmp是为了调整输出格式使用,否则只需循环子节点递归打印即可
if(0 == m_childList.size()) {
std::cout << "-" << this->ObjectName() << std::endl;
tab = tmp;
return;
}
std::cout << "-" << this->ObjectName() << std::endl;
for(MyObject *obj : m_childList) {
tmp = tab;
tab += "--";
std::cout << tab;
obj->dumpObjectTree();
}
tab = "" + tmp.erase(0, 2);
}
//增加直接子节点对象
void MyObject::addChild(MyObject *child)
{
m_childList.push_back(child);
}
//删除直接子节点对象
void MyObject::removeChild(MyObject *child)
{
m_childList.remove(child);
}
main.cpp
#include <iostream>
#include "myobject.h"
using namespace std;
int main()
{
//模拟创建一棵对象树
MyObject parent;
parent.setObjectName("parent");
MyObject *child_0 = new MyObject(&parent);
child_0->setObjectName("child_0");
MyObject *child_0_0 = new MyObject(child_0);
child_0_0->setObjectName("child_0_0");
MyObject *child_0_1 = new MyObject(child_0);
child_0_1->setObjectName("child_0_1");
MyObject *child_1 = new MyObject(&parent);
child_1->setObjectName("child_1");
cout << "Object Tree print" << endl;
parent.dumpObjectTree();
cout << endl;
delete child_0; //子节点先于父节点释放
child_0 = nullptr;
return 0;
}
运行结果:
如上所示我们就用C++实现并测试了一个简单的对象树,需使用对象树的对象只需继承上面的MyObject类即可方便的使用对象树,大家也可以自行测试,若有疑问欢迎留言讨论。