Qt中容器类应该如何存储对象
Qt提供了丰富的容器类型,如:QList、QVector、QMap等等。详细的使用方法可以参考官方文档,网上也有很多示例文章,不过大部分文章的举例都是使用基础类型:如int、QString等。如果我们要存储一个对象类型,应该如何做呢?—— 当然是和int类型一样操作,因为这些容器类都是泛型的。不过,我们今天要讨论的不是容器类的使用用法,而是容器存储的对象内存如何释放的问题。
(这里提到了对象类型是指 Class/Struct,可以继承自QObject,也可以是普通的C++类。)
下面以QList<T>为例,直接通过代码来看一下以下几种情况的内存释放。
0.测试前的一点点准备
1 // testobj.h 2 #ifndef TESTOBJ_H 3 #define TESTOBJ_H 4 5 #include <QObject> 6 7 // 测试对象(也可以不继承QObject) 8 class TestObj : public QObject 9 { 10 Q_OBJECT 11 public: 12 explicit TestObj(QObject *parent = 0); 13 14 ~TestObj(); 15 16 TestObj(const TestObj& obj); 17 18 TestObj& operator=(const TestObj& obj); 19 20 }; 21 22 #endif // TESTOBJ_H
实现TestObj
1 // testobj.cpp 2 #include "testobj.h" 3 #include <QDebug> 4 5 // 构造时输出log 6 TestObj::TestObj(QObject *parent) : QObject(parent) 7 { 8 qDebug()<<"TestObj C.tor."; 9 } 10 11 // 析构时输出log 12 TestObj::~TestObj(){ 13 qDebug()<<"TestObj D.tor."; 14 } 15 16 // 拷贝时输出log 17 TestObj::TestObj(const TestObj& obj){ 18 19 qDebug()<<"TestObj COPY."; 20 } 21 22 // 赋值时输出log 23 TestObj& TestObj::operator=(const TestObj& obj){ 24 25 qDebug()<<"TestObj =."; 26 return *this; 27 }
1.在栈上创建对象,然后添加容器
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 5 #include "testobj.h" 6 7 int main(int argc, char *argv[]) 8 { 9 QCoreApplication a(argc, argv); 10 11 { 12 // Test one 13 { 14 TestObj obj; 15 { 16 QList<TestObj> objList; 17 objList.append(obj); 18 } 19 20 qDebug()<<"ONE: "<<"objList release."; 21 } 22 23 qDebug()<<"ONE: "<<"TestObj release."; 24 qDebug()<<endl; 25 } 26 27 return a.exec(); 28 }
运行结果:
结论:
对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构。
2. 在堆上创建对象,然后添加到容器
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 5 #include "testobj.h" 6 7 int main(int argc, char *argv[]) 8 { 9 QCoreApplication a(argc, argv); 10 11 { 12 // test tow 13 { 14 TestObj *obj = new TestObj; 15 { 16 QList<TestObj*> objList; 17 objList.append(obj); 18 } 19 qDebug()<<"TWO: "<<"objList release."; 20 } 21 22 qDebug()<<"TWO: "<<"TestObj release? NO!"; 23 qDebug()<<endl; 24 } 25 26 return a.exec(); 27 }
运行结果:
结论:
对象不会发生拷贝,但容器析构后容器内的对象并未析构
3. 使用Qt智能指针来管理堆上的对象,然后添加到容器
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 #include <QSharedPointer> 5 6 #include "testobj.h" 7 8 int main(int argc, char *argv[]) 9 { 10 QCoreApplication a(argc, argv); 11 12 { 13 // test three 14 { 15 QSharedPointer<TestObj> obj(new TestObj); 16 { 17 QList<QSharedPointer<TestObj>> objList; 18 objList.append(obj); 19 } 20 qDebug()<<"THREE: "<<"objList release."; 21 } 22 23 qDebug()<<"THREE: "<<"TestObj release? YES!"; 24 qDebug()<<endl; 25 } 26 27 return a.exec(); 28 }
运行结果:
结论:
对象不会发生拷贝,容器析构的时候,容器内对象并未析构,但超过作用域后,智能指针管理的对象会析构。
4.给测试对象一个parent(父对象),然后进行上述测试
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 5 #include "testobj.h" 6 7 int main(int argc, char *argv[]) 8 { 9 QCoreApplication a(argc, argv); 10 11 { 12 // test four 13 { 14 QObject root; 15 TestObj *obj = new TestObj(&root); 16 { 17 QList<TestObj*> objList; 18 objList.append(obj); 19 } 20 qDebug()<<"FOUR: "<<"objList release."; 21 } 22 23 qDebug()<<"FOUR: "<<"TestObj release? YES!"; 24 qDebug()<<endl; 25 } 26 27 return a.exec(); 28 }
运行结果:
结论:
这里的root对象起到了类似智能指针的作用,这也是Qt的一个特性,即在父对象析构的时候,会将其左右子对象析构。(注意:普通C++对象并无此特性))
5.将QList作为测试对象的parent,然后进行上述测试
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 #include <QSharedPointer> 5 6 #include "testobj.h" 7 8 int main(int argc, char *argv[]) 9 { 10 QCoreApplication a(argc, argv); 11 12 { 13 // test five 14 { 15 { 16 QList<TestObj*> objList; 17 TestObj *obj = new TestObj(&objList); // Error: QList<> is NOT a QObject. 18 objList.append(obj); 19 } 20 qDebug()<<"FIVE: "<<"objList release."; 21 qDebug()<<"FIVE: "<<"TestObj release? ERROR!"; 22 } 23 24 qDebug()<<endl; 25 } 26 27 return a.exec(); 28 }
测试结果:
1 // 编译错误,因为QList并不继承自QObject,所以不能作为TestObj的parent
结论:
// qlist.h
// qbytearraylist.h
QList并不是QObject,只是普通的模板类
6.扩展一下 QList,继承QObject
1 // testobjlist.h 2 #ifndef TESTOBJLIST_H 3 #define TESTOBJLIST_H 4 5 #include <QObject> 6 #include <QList> 7 8 class TestObj; 9 10 class TestObjList : public QObject, public QList<TestObj*> 11 { 12 Q_OBJECT 13 public: 14 explicit TestObjList(QObject *parent = 0); 15 ~TestObjList(); 16 }; 17 18 #endif // TESTOBJLIST_H
1 // testobjlist.cpp 2 #include "testobjlist.h" 3 #include "testobj.h" 4 #include <QDebug> 5 6 TestObjList::TestObjList(QObject *parent) : QObject(parent) 7 { 8 qDebug()<<"TestObjList C.tor."; 9 } 10 11 TestObjList::~TestObjList() 12 { 13 qDebug()<<"TestObjList D.tor."; 14 }
测试:
1 // main.cpp 2 #include <QCoreApplication> 3 #include <QList> 4 #include <QSharedPointer> 5 6 #include "testobj.h" 7 #include "testobjlist.h" 8 9 int main(int argc, char *argv[]) 10 { 11 QCoreApplication a(argc, argv); 12 13 { 14 // test six 15 { 16 { 17 TestObjList objList; 18 TestObj *obj = new TestObj(&objList); // Error: QList<> is NOT a QObject. 19 objList.append(obj); 20 } 21 qDebug()<<"SIX: "<<"objList release."; 22 qDebug()<<"SIX: "<<"TestObj release? YES!"; 23 } 24 25 qDebug()<<endl; 26 } 27 28 return a.exec(); 29 }
测试结果:
结论:
TestObjList 释放的时候会释放其内部的对象
7.附加测试
1 { 2 TestObjList objList; 3 TestObjList list2 = objList; // Error: QObject Q_DISABLE_COPY 4 }
结论:
Qt为了防止开发者出错,将QObject的类拷贝构造函数和赋值操作符都DISABLE了。这样做的好处是,一旦开发者不小心定义了一个QList<QObject>的容器,在添加对象时就会得到一个编译错误,从而避免发生隐式拷贝。
总结,使用容器类存储对象时,最好使用对象指针类型,如:QList<TestObj*>,而不要使用 QList<TestObj> 这样的定义。建议采用 智能指针QSharedPointer 或 为对象设置parent 的方法来管理内存,避免内存泄露。