Qt 之 智能指针汇总
來源
還有其他一些,做了一些匯總和測試,就不全列了。
文章目錄:
一、垂懸指針的問題
二、Qt中的智能指針
???? ??1、QPointer
???? ??2、QSharedPointer & QWeakPointer
???? ??3、QScopedPointer
???? ??4、其他智能指針
三、實踐記錄
四、用法舉例
???? ??1、QWeakPointer舉例
???? ??2、QScopedPointer 與 std::unique_ptr 舉例
???? ??3、測試樣例
代碼中出現一個bug,最終發現是由于在某個特殊情況下出現了使用垂懸指針,造成了程序崩潰,進而學習了解了Qt的智能指針機制。
一、懸垂指針的問題
如圖,有兩個指針a和b指向同一片內存,如果刪除其中一個指針a,再去使用指針b的話,程序會崩潰。因為指針b此時已經是一個垂懸指針(Dangling pointer)了,它指向的內存已經被釋放不再有效。
垂懸指針使用指針b之前先判斷b是否為空,這個做法在這里是不起作用的。問題的本質是通過指針a去釋放內存時,指針b沒有同步地置為空。
假如指針b能隨內存的釋放而自動置為空就好了,這正是智能指針所要解決的問題。
二、Qt中的智能指針
Qt提供了若干種智能指針:QPointer、QSharedPointer、QWeakPointer、QScopedPointer、QScopedArrayPointer、QSharedDataPointer、QExplicitlySharedDataPointer。
注:1、筆者Qt版本為4.8; 2、下述示例代碼中"Plot"為"QObject"類的子類。
1、QPointer
QPointer只用于QObject的實例。如果它指向的對象被銷毀,它將自動置空。
如圖:
QPointer這是Qt體系下的專門用于QObject的智能指針。常見使用方法:
QPointer<Plot> a(new T()); //構造 QPointer<Plot> a(b); //構造 a.isNull(); //判空 a.data(); //返回裸指針2、QSharedPointer & QWeakPointer
QSharedPointer是引用計數(強)指針,當所有強指針銷毀時,實際對象才會銷毀。QWeakPointer是弱指針,可以持有對QSharedPointer的弱引用。它作為一個觀察者,不會引起實際對象銷毀,當對象銷毀時會自動置空。
這兩種指針同時都有以下3個成員:強引用計數strongRef,弱引用計數weakRef和數據data。
Qt引用計數指針
QWeakPointer兩種指針分別對應于C++中的std::shared_ptr和std::weak_ptr。常見使用方法://構造
//使用
c.clear(); //清除 a.isNull(); //判空 a->func(...); //(按常規指針來使用 "->") QSharedPointer<Plot> e = d.toStrongRef(); //弱指針轉為強指針。注意,弱指針無法操縱數據,必須轉為強指針 QWeakPointer<Plot> f = e.toWeakRef();//強指針顯式轉為弱指針 QSharedPointer<Plot> g = e.dynamicCast<T>(); //動態類型轉換3、QScopedPointer
QScopedPointer保證當當前范圍消失時指向的對象將被刪除。它擁有一個很好的名字,它向代碼的閱讀者傳遞了明確的信息:這個智能指針只能在本作用域里使用,不希望被轉讓,因為它的拷貝構造和賦值操作都是私有的。相當于C++中的std::unique_ptr,實例代碼:
func(){Plot* plot = new Plot();//QScopedPointer出作用域自動銷毀內存QScopedPointer<Plot>qsp(plot);//plot沒有內存泄漏 }4、其他智能指針
QScopedArrayPointer:一個QcopedPointer,默認刪除它指向Delete []運算符的對象。為方便起見,還提供了操作符[]。QSharedDataPointer/QExplicitySharedDataPointer搭配QSharedData類一起使用,以實現自定義隱式共享或顯式共享類。
三、實踐記錄
1、通常,要使用弱指針,必須將其轉換為強指針,因為這樣的操作確保了只要您使用它就會生存。這相當于“鎖定”訪問的對象,并且是使用弱指針指向的對象的唯一正確方法。并且轉換后使用前需要判空。
QSharedPointer<Plot> qsp = qwp.toStrongRef(); //qwp是QWeakPointer if(!qsp.isNull()){ qDebug() << qsp->getName(...); //使用指向的對象//... }2、最好在new的時候就用QSharedPointer封裝,并管理起來。
QSharedPointer<Plot> qsp = QSharedPointer(new Plot());3、使用智能指針包裝后,不要直接去刪除指針對象。
Plot* plot = new Plot(); QSharedPointer<Plot> qsp1(plot); delete plot; //運行時會提示:"shared QObject was deleted directly. The program is malformed and may crash."4、不要多次使用同一裸指針構造QSharedPointer。Plot *plot = new Plot();
QSharedPointer<Plot> qsp(plot); QSharedPointer<Plot> qsp_ok = qsp; QSharedPointer<Plot> qsp_error(plot); //崩潰,輸出: “pointer 0x1f0a8f0 already has reference counting”5、不要使用new直接構造QWeakPointer對象,它只能通過QSharedPointer的賦值來創建。QWeakPointer<Plot> a(new Plot()); //error: 引用計數:強-1 弱2
6、由于智能指針對象是值語義,參數傳遞時盡可能用const引用兼顧效率
7、關于動態轉換。使用QSharedPointer::dynamicCast()方法。
8、關于智能指針與QVariant轉換,并關聯到qobject的userdata。//注冊到元對象
9、關于Qt元對象系統自動析構和Qt智能指針自動析構相沖突的問題,經初步實驗Qt4.8中應該已經解決了?不過實際中,可以讓數據用智能指針管理,不用父子層級;窗體控件用父子層級,不用智能指針。
四、 用法舉例
1、QWeakPointer舉例
QWeakPointer不能用于直接取消引用指針,但它可用于驗證指針是否已在另一個上下文中被刪除。并且QWeakPointer對象只能通過QSharedPointer的賦值來創建。
需要注意的是,QWeakPointer不提供自動轉換操作符來防止錯誤發生。即使QWeakPointer跟蹤指針,也不應將其視為指針本身,因為它不能保證指向的對象保持有效。
說了那么多,QWeakPointer到底有什么用呢?
答案就是:解除循環引用。
在概述中我們說到,QWeakPointer 是為配合 QSharedPointer 而引入的一種智能指針。而什么叫循環引用,就是說:兩個對象互相使用一個 QSharedPointer成員變量指向對方(你中有我,我中有你)。由于QSharedPointer是一個強引用的計數型指針,只有當引用數為0時,就會自動刪除指針釋放內存,但是如果循環引用,就會導致QSharedPointer指針的引用永遠都不能為0,這時候就會導致內存無法釋放。
所以QWeakPointer誕生了,它就是為了打破這種循環的。并且,在需要的時候變成QSharedPointer,在其他時候不干擾QSharedPointer的引用計數。它沒有重載 * 和 -> 運算符,因此不可以直接通過 QWeakPointer 訪問對象,典型的用法是通過 lock() 成員函數來獲得 QSharedPointer,進而使用對象。
示例
首先,我們來看一個QSharedPointer循環使用的示例:
在構造函數中調用test()函數,執行完過后應該會自動釋放parent和children對象,但是由于相互引用,在退出之前,引用計數為2,退出之后引用計數還是1,所以導致不能自動釋放,并且此時這兩個對象再也無法訪問到。運行過后發現并沒有進入到Parent和Children的析構函數中。這就導致了內存泄漏。
那么該如何解決這個問題呢?
很簡單,直接將上述代碼中的
改成
QWeakPointer<Children> m_pChildren; QWeakPointer<Parent> m_pParent;這時候再次運行,就會看到輸出:
~Children ~Parent這是因為在test()退出之前引用計數是1,函數退出之后就自動析構,這就解除了上面的循環引用。
2、QScopedPointer 與 std::unique_ptr
它們概念上應該是是一樣的。下面不再區分:
這是一個很類似auto_ptr的智能指針,它包裝了new操作符在堆上分配的動態對象,能夠保證動態創建的對象在任何時候都可以被正確地刪除。但它的所有權更加嚴格,不能轉讓,一旦獲取了對象的管理權,你就無法再從它那里取回來。
無論是QScopedPointer 還是 std::unique_ptr 都擁有一個很好的名字,它向代碼的閱讀者傳遞了明確的信息:這個智能指針只能在本作用域里使用,不希望被轉讓。因為它的拷貝構造和賦值操作都是私有的,這點我們可以對比QObject及其派生類的對象哈。
用法 (來自Qt的manual):
考慮沒有智能指針的情況,void myFunction(bool useSubClass){MyClass *p = useSubClass ? new MyClass() : new MySubClass;QIODevice *device = handsOverOwnership();if (m_value > 3) {delete p;delete device;return;}try {process(device);}catch (...) {delete p;delete device;throw;}delete p;delete device;}我們在異常處理語句中多次書寫delete語句,稍有不慎就會導致資源泄露。采用智能指針后,我們就可以將這些異常處理語句簡化了:
void myFunction(bool useSubClass){QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);QScopedPointer<QIODevice> device(handsOverOwnership());if (m_value > 3)return;process(device);}另,我們一開始的例子,也是使用這兩個指針的最佳場合了(出main函數作用域就將其指向的對象銷毀)。
注意:因為拷貝構造和賦值操作私有的,它也具有auto_ptr同樣的“缺陷”——不能用作容器的元素。
3、測試樣例
#include <QCoreApplication> #include <QSharedDataPointer> #include <QSharedPointer> #include <QWeakPointer> #include <QTimer> #include <QDebug> int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);//raw pointer//QString *p = new QString("hello");QSharedPointer<QString> a(new QString("hello"));//QSharedPointer<QString> b = a;QWeakPointer<QString> c = a; //強指針構造弱指針QWeakPointer<QString> d(a);//使用a.clear();//c.clear(); //清除if(!c.toStrongRef().isNull()){qDebug() << c.isNull() << c.toStrongRef()->length();}else {qDebug() << "is null";}qDebug() << a.isNull(); //判空qDebug() << a->length()<<*(a.data()); //(按常規指針來使用 "->")QSharedPointer<QString> e = d.toStrongRef(); //弱指針轉為強指針。注意,弱指針無法操縱數據,必須轉為強指針QWeakPointer<QString> f = e.toWeakRef();//強指針顯式轉為弱指針QSharedPointer<QString> g = e.dynamicCast<QString>(); //動態類型轉換 // QScopedPointer<QString> t2(p); // QScopedPointer<QString> t3(p);// t3.reset(); // qDebug() << t3.data(); // qDebug() << *(t1.data()); // qDebug() << *(t2.take()->data());//Implements non-reference-counted strong pointer // QScopedPointer<QString> pScopedPointer(new QString("Scoped")); // // Build error, can NOT be shared and reference-counted // //QScopedPointer<QString> pScopedPointerpScopedPointer2 = pScopedPointer; // //Implements reference-counted strong sharing of pointers // QSharedPointer<QString> pSmart(new QString("Smart")); // QSharedPointer<QString> pSmart2; // pSmart2 = QSharedPointer<QString>(new QString("smart 2")); // QSharedPointer<QString> pSharedPoninter; // // can be shared safely and reference-counted // pSharedPoninter = pSmart; // qDebug() << *(pSmart.data()); // qDebug() << *(pSmart2.data()); // qDebug() << *(pSharedPoninter.data()); // qDebug() << pSharedPoninter.stro // QTimer *t = new QTimer; // QSharedPointer<QTimer> timer(t); // QWeakPointer<QTimer> pWeakPointer = timer; // pWeakPointer.data()->start(2000); // //Weak pointer's resources can be deleted from outside world // delete t; // qDebug() << timer.data(); // if (pWeakPointer.isNull()) // { // qDebug() << "contained QObject has been deleted"; // } }總結
以上是生活随笔為你收集整理的Qt 之 智能指针汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1024. 视频拼接 的两种解法
- 下一篇: 用pdf压缩软件压缩pdf文件的方法