1. 程式人生 > >Qt中容器類應該如何存儲對象

Qt中容器類應該如何存儲對象

容器 qstring 指針 lease public 泛型 argc pre gif

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

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

Qt中容器類應該如何存儲對象