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

