天天看點

Qt平台下C++記憶體管理 2021-08-10

01 程式設計碎念我的程式設計觀念,是在公司磚海中,受導師和大咖影響逐漸形成。文中觀點均來源于真實的技術實踐,部分理念也是繼承前人的設計成果,如有侵犯的地方請告知。在此特别感謝給予我點撥的人,站在大咖的肩膀上,讓我們看得更遠。文中提到的程式設計觀念,大部分是基于C++實作的基礎技術,有Qt架構相關的,也有設計模式、面向對象相關的。可能大家會想,現在都是AI雲的時代了,這些太老了。但我認為,這些才是程式設計的基礎,這就是正确的程式設計世界觀。

02 記憶體管理某日,我在調試bug的時候,發現Qt源碼中已經少有“”形式的指針了。回想公司的代碼,大多寫于2010年,滿滿的星,我閉了一下眼睛,感覺到熟悉的腐敗味道。2019年接手一個項目,一個反複無常近乎夭折的項目。在此之前,已經有同僚在裡面掙紮了幾月,眼看要釋出版本了,壓力測試卻不能運作3時。C++程式設計中,記憶體一直就是顯著的問題。在Qt平台中,我推薦使用Qt智能指針、對象樹、大記憶體技術來進行記憶體管理。

03智能指針範圍指針QScopedPointerQScopedPointer p(new MyClass ());Scope為“範圍”的意思。可以了解為,QScopedPointer對象p管理的記憶體,在p生命周期内有效。這是一個在項目中經常使用的智能指針,經常使用的場景:1、類的成員指針

class MyPrivateClass; 
class MyClass
{
  private:
      QScopedPointer<MyPrivateClass> privatePtr; 
  public:
      MyClass(); 
      inline ~MyClass() {} 
};


           

注意事項:QScopedPointer禁止與QObject對象樹中的對象組合使用,對象樹中parent釋放的時候,會釋放其子節點,QScopedPointer對象再次釋放child記憶體時,會導緻錯誤。

對象追蹤指針QPointerQPointer label = new QLabel(parent);針對QObject的追蹤指針,QPointer并不會釋放目标對象,用意在于記錄QObject,以便在其他成員函數中使用。

QPointer就像一個GPS定位器,在目标對象被釋放後,自動置為0,即parent析構時析構子對象QLabel,此後label記錄的記憶體為0。

需要注意管理的目标對象必須從QObject派生。這是一個在項目中經常使用的智能指針,經常使用的場景:

1、 QObject子對象,作為成員指針時

2、 記錄非自身管理的QObject對象指針時共享指針QSharedPointer和QWeakPointerQSharedPointer p(new MyClass ());共享指針,意為多個QSharedPointer共享同個記憶體對象。

當p自身被釋放時,會判斷自身管理的記憶體對象,是否有被其他的QSharedPointer引用共享,如果沒有則釋放管理的記憶體對象。經常使用的場景:

1、類的成員指針,特别是容器類型中使用,減少了對容器記憶體對象的釋放操作,容器的增删操作,QSharedPointer保證其管理的記憶體被釋放。注意事項:将QSharedPointer管理的對象指針A,注冊到其他需要管理A生命周期的類中,會發生錯誤。

比如postEvent的QEvent對象使用QSharedPointer管理的話,QSharedPointer退出作用域時會釋放QEvent,導緻postEvent處理過程中對QEvent的通路,或處理完事件後對QEvent的釋放,都是錯誤的。

弱共享指針QWeakPointer剛剛接觸Qt的同學,在網上搜尋文章時,對弱共享可能一知半解。

QWeekPointer可以了解為與QPointer一樣的追蹤指針,QPointer目标是記錄和追蹤QObject對象,QWeekPointer則追蹤的是QSharedPointer對象。我在項目中通常很少使用弱共享指針,它的目的是作為QSharedPointer助手類,解除QSharedPointer的循環依賴,在面向對象設計中,出現循環依賴本身就不是一件理所當然的事。

關于智能指針的一些思考智能指針是記憶體管理的工具,在文法層次上,為C++工程師降低了記憶體管理難度,能有效預防不經意筆誤導緻的記憶體洩漏,并減少了記憶體管理代碼量。

但是,智能指針本質上并不能解決項目中記憶體管理問題。作為C++工程師,必須了解記憶體管理機制,明确的知道記憶體該何時建立、何時銷毀。

否則在選擇哪種智能指針,用于管理目标記憶體時,不能做出正确的決定,還是會導緻項目記憶體異常。除非未來Qt的某個版本,隻有一個智能指針QPointer。

對于記憶體問題的解決,實際項目中我們更依靠QObject對象樹和記憶體池。

04 對象樹

Qt中用樹形結構描述對象之間的父子關系,如下圖,P為樹的父節點,A2為一級子節點,B1、B2為二級子節點。

Qt平台下C++記憶體管理 2021-08-10

對象樹記憶體的管理,當A2釋放時,先釋放B1、B2,再将自身從P的子節點清單中移除。Qt對象樹的特征,確定在父對象釋放時,釋放子對象記憶體,在實際項目中,我會經常使用。QObject對象樹的實作很容易了解,QObjectData中記錄指向父對象的指針parent,和一個QList<QObject*>子對象清單children。在UML中QObject描述為自己組合自己,意外不意外。

Qt平台下C++記憶體管理 2021-08-10

QObject延遲釋放deleteLater在QObject對象機制中,有一個非常好用的特性,deleteLater,其作用是延遲對象的析構。什麼意思呢,調用deleteLater後,QObject對象不會馬上析構,而是在下一個消息循環析構。使用的場景,比如容器中子控件的替換,可以避免畫面閃爍,或者是其他一些需要延遲釋放的地方。

05 記憶體池

記憶體池大記憶體通常也叫記憶體池,其實記憶體池中管理的不僅僅是大記憶體,還有小記憶體。在項目中,出于2種目的使用記憶體池,一是性能提升,二是避免記憶體碎片。

性能提升

對于頻繁申請-釋放小塊記憶體的情況,使用記憶體池比new有顯著的性能提升,但僅僅出于此目的,并沒有充足的理由用到記憶體池。

避免記憶體碎片

對于記憶體池技術,更多的需要是避免記憶體碎片。我在項目中曾經遇到,申請20M記憶體失敗的情況,而此時系統記憶體明顯大于20M,為什麼呢,因為系統記憶體被碎片切割了,沒有一塊連續的20M記憶體。

Qt平台下C++記憶體管理 2021-08-10

在系統記憶體管理不夠“聰明”時,C++程式員會有強烈的掌控欲望,希望一切都掌控在自己手中,包括記憶體的潛在風險。

通過記憶體池技術,在碎片切割導緻記憶體不連續時,我們的程式有機會重新整理記憶體,滿足申請大小。

Qt平台下C++記憶體管理 2021-08-10

關于記憶體池的技術,Qt并沒有提供實作,但有很多開源的庫參考,可以搜尋“記憶體池”。

Qt

繼續閱讀