Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

Qt提供了丰富的容器类型,如:QList、QVector、QMap等等。详细的使用方法可以参考官方文档,网上也有很多示例文章,不过大部分文章的举例都是使用基础类型:如int、QString等。如果我们要存储一个对象类型,应该如何做呢?—— 当然是和int类型一样操作,因为这些容器类都是泛型的。不过,我们今天要讨论的不是容器类的使用用法,而是容器存储的对象内存如何释放的问题。

(这里提到了对象类型是指 Class/Struct,可以继承自QObject,也可以是普通的C++类。)

下面以QList<T>为例,直接通过代码来看一下以下几种情况的内存释放。

0.测试前的一点点准备

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

实现TestObj

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

1.在栈上创建对象,然后添加容器

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

运行结果:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

结论:

对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构。

2. 在堆上创建对象,然后添加到容器 

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

运行结果:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

结论:

对象不会发生拷贝,但容器析构后容器内的对象并未析构

3. 使用Qt智能指针来管理堆上的对象,然后添加到容器

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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

运行结果:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

结论:

对象不会发生拷贝,容器析构的时候,容器内对象并未析构,但超过作用域后,智能指针管理的对象会析构。

4.给测试对象一个parent(父对象),然后进行上述测试

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

运行结果:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

结论:

这里的root对象起到了类似智能指针的作用,这也是Qt的一个特性,即在父对象析构的时候,会将其左右子对象析构。(注意:普通C++对象并无此特性))

5.将QList作为测试对象的parent,然后进行上述测试

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 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

测试结果:

1 // 编译错误,因为QList并不继承自QObject,所以不能作为TestObj的parent

结论:

// qlist.h

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

// qbytearraylist.h

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

QList并不是QObject,只是普通的模板类

6.扩展一下 QList,继承QObject

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

测试:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)
 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 }
Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

测试结果:

Qt中容器类应该如何存储对象(对象加入到容器时会发生拷贝,容器析构时,容器内的对象也会析构)

结论:

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 的方法来管理内存,避免内存泄露。

https://www.cnblogs.com/pyw0818/p/8039233.html

上一篇:【SICP归纳】2 高阶函数和数据抽象


下一篇:Grunt JS构建环境搭建以及使用入门