日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【精华】详解Qt中的内存管理机制

發布時間:2023/12/10 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【精华】详解Qt中的内存管理机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

內存管理,是對軟件中內存資源的分配與釋放進行有效管理的方法和理論。

眾所周知,內存管理是軟件開發的一個重要的內容。軟件規模越大,內存管理可能出現的問題越多。如果像C語言一樣手動地管理內存,一會給開發人員帶來巨大的負擔,二是手動管理內存的可靠性較差。

Qt為軟件開發人員提供了一套內存管理機制,用以替代手動內存管理。

下面開始逐條講述Qt中的內存管理機制。

一脈相承的棧與堆的內存管理

了解C語言的同學都知道,C語言中的內存分配有兩種形式:棧內存、堆內存。

棧內存

棧內存的管理是由編譯器來做的,棧上申請的內存變量,生存期由所在作用域決定,超出作用域的棧內存變量會被編譯器自動釋放。

值得一提的是,作用域的顯著標志是一對大括號,大括號內部即為作用域內部,大括號外部即為作用域外部。

參考下列代碼:

int main() {int a = 0;return 1; }

變量a在棧內存上,main函數返回時,作用域結束,a的內存自動被釋放。

從以上描述也可以看出,棧內存的使用是在編譯器嚴密監管之下進行的,遵循嚴格的作用域規則,所以棧內存的大小、申請時機、釋放時機都能在編譯的時候確定。

堆內存

堆內存是另外一種管理方式。堆內存最大的特點是可以動態分配,即在運行時可以根據需要進行申請。當然隨之而來的弊端也顯而易見:需要開發人員對堆內存的釋放進行嚴格管理,稍有疏漏會導致內存泄漏,甚至軟件崩潰等問題。

參考下列代碼:

int main() {// 申請堆內存int *intArray = (int *)malloc(100);// 使用堆內存...// 釋放堆內存free(intArray);return 1; }

如上述代碼,堆內存分配的寫法區別于棧內存。C語言中,堆內存使用malloc分配,使用free釋放。C++中可以使用new分配,使用delete釋放。

至此,我們介紹了C語言中的內存管理方式。我們知道Qt是C++的框架,C++是對C語言的擴展,所以C語言中的內存管理方式(堆、棧)和動態內存管理(堆內存釋放問題)存在的問題,在C++中仍然存在。所以Qt中自然而然也有相同的問題。說起來可能有點亂,下面用一張圖來說明它們的關系:

那么,Qt是如何為我們解決動態內存管理問題的呢?下面開始正式講解。

使用對象父子關系進行內存管理

使用對象父子關系進行內存管理的原理,簡述為:

在創建類的對象時,為對象指定父對象指針。當父對象在某一時刻被銷毀釋放時,父對象會先遍歷其所有的子對象,并逐個將子對象銷毀釋放。

為了直觀理解上述過程,以如下代碼為例進行說明:

#include <QApplication> #include <QLabel>int main(int argc, char *argv[]) {QApplication a(argc, argv);// 創建主窗口QWidget mainWidget;mainWidget.resize(400, 300);// 創建文字標簽QLabel *label = new QLabel("Hello World!", &mainWidget);// 顯示主窗口mainWidget.show();return a.exec(); }

運行結果如下:

上述代碼中,mainWidget為主窗口對象,類型為QWidgetlabel為子窗口對象,類型為QLabel *。

注意代碼第13行,在創建label文本標簽窗口對象時,new QLabel的第二個參數即為父對象地址(參考Qt Assistant中QLabel的說明文檔),這里給的值是主窗口的地址。

main函數退出時,mainWidget超出main函數作用域會析構,析構時會自動刪除label窗口對象,所以這里,我們不需要再寫一行:delete label; 來釋放label的內存,很方便而且又能節省時間精力。

使用引用計數對內存進行管理

引用計數

引用計數可以說是軟件開發人員必知必會的知識點,它在內存管理領域的地位是數一數二的。

引用計數的原理,還是力所能及地用最簡單的話來描述:

引用計數需要從三個方面來全面理解:

  • 使用場景:一個資源,多處使用(使用即引用)。

  • 問題:到底誰來釋放資源。

  • 原理:使用一個整形變量來統計,此資源在多少個地方被使用,此變量稱為引用計數。當某處使用完資源以后,將引用計數減1。當引用計數為0時,即沒有任何地方再使用此資源時,真正釋放此資源。這里的資源,在動態內存管理中就是指堆內存。

  • 用一句話描述就是:誰最后使用資源,誰負責釋放資源

    我們很容易聯想到現實中的例子,就是日常生活中的刷碗問題的解決方案,即誰最后吃完誰刷碗。

    需要說明的是,引用計數不僅僅是在內存管理中使用,它是一個通用的機制,凡是涉及到資源管理的問題,都可以考慮使用引用計數。

    下面將要介紹基于引用計數原理的兩種衍生的機制:顯式共享和隱式共享。

    顯式共享

    顯式共享,是僅僅使用引用計數控制資源的生命周期的一種共享管理機制。這種機制下,無論資源在何處被引用,自始至終所有引用指向資源都是同一個。

    之所以叫顯式共享,是因為這種共享方式很直接,沒有隱含的操作,如:Copy on Write寫時拷貝(見隱式共享的相關說明)。如果想要拷貝并建立新的引用計數,必須手動調用detach()函數。

    從使用者的角度看,從頭到尾資源只有一份,一個地方修改了,另一個地方就能讀取到修改后的資源。

    **相關Qt類:**QExplicitlySharedDataPointer,更加深入的用法和編碼,需要參考Qt文檔中的相關說明及Demo。

    隱式共享

    隱式共享,也是一種基于引用計數的控制資源的生命周期的共享管理機制。

    隱式共享,對不同的操作有不同的處理:

    • 讀取時,在所有引用的地方使用同一個資源;

    • 在寫入、修改時自動復制一份資源出來做修改,自動脫離原始的引用計數,因為是新的資源,所以要建立新的引用計數。這種操作叫Copy on Write寫時復制技術,是自動隱含進行的。

    從使用者的角度看,每個使用者都像是擁有獨立的一份資源。在一個地方修改,修改的只是原始資源的拷貝,不會影響原始資源的內容,自然就不會影響到其他使用者。所以這種共享方式稱為隱式共享。

    相關Qt類有QString、QByteArray、QImage、QList、QMap、QHash等。

    推薦閱讀:Qt文檔中的Implicit Sharing專題。

    智能指針

    智能指針是對C/C++指針的擴展,同樣基于引用計數。

    智能指針和顯示共享和隱式共享有何區別?它們區別是:智能指針是輕量級的引用計數,它將顯式共享、隱式共享中的引用計數實現部分單獨提取了出來,制作成模板類,形成了多種特性各異的指針。

    例如,QString除了實現引用計數,還實現了字符串相關的豐富的操作接口。QList也實現了引用計數,還實現了列表這種數據結構的各種操作。可以說,顯式共享和隱式共享一般是封裝在功能類中的,不需要開發者來管理。

    智能指針將引用計數功能剝離出來,為Qt開發者提供了便捷的引用計數基礎設施。

    強(智能)指針

    Qt中的強指針實現類是:QSharedPointer,此類是模板類,可以指向多種類型的數據,主要用來管理堆內存。關于QSharedPointer在Qt Assistant中有詳細描述。

    它的原理和顯式共享一樣:最后使用的地方負責釋放刪除資源,如類對象、內存塊。

    強指針中的“強”,是指每多一個使用者,引用計數都會老老實實地**+1**。而弱指針就不同,下面就接著講解弱指針。

    弱(智能)指針

    Qt中的弱指針實現類是QWeakPointer,此類亦為模板類,可以指向多種類型的數據,同樣主要用來管理堆內存。關于QWeakPointer在Qt Assistant中有詳細描述。

    弱指針只能從強指針QSharedPointer轉化而來,獲取弱指針,不增加引用計數,它只是一個強指針的觀察者,觀察而不干預。只要強指針存在,弱指針也可以轉換成強指針。可見弱指針和強指針是一對形影不離的組合,通常結合起來使用。

    局部指針

    局部指針,是一種超出作用域自動刪除、釋放堆內存、對象的工具。它結合了棧內存管理和堆內存管理的優點。

    Qt中的實現類有:QScopedPointer,QScopedArrayPointer,具體可以參考Qt Assistant。

    觀察者指針

    上面說弱指針的時候,講到過觀察者。觀察者是指僅僅做查詢作用的指針,不會影響到引用計數。

    Qt中的觀察者指針是QPointer,它必須指向QObject的子類對象,才能對對象生命周期進行觀察。因為只有QObject子類才會在析構的時候通知QPointer已失效。

    QPointer是防止懸掛指針(即野指針)的有效手段,因為所指對象一旦被刪除,QPointer會自動置空,在使用時,判斷指針是否為空即可,不為空說明對象可以使用,不會產生內存訪問錯誤的問題。

    總結

    本篇文章講解了Qt中的各種內存管理機制,算是做了一個比較全面的描述。

    之所以說是必讀,是因為筆者在工作中發現,內存管理確實非常重要。Qt內存管理機制是貫穿整個Qt中所有類的核心線索之一,搞懂了內存管理

    • 能在腦海中形成內存中對象的布局圖,寫代碼的時候才能下筆如有神,管理起項目中眾多的對象才能游刃有余,提高開發效率;
    • 能夠減少bug的產生。有經驗的開發者應該知道,內存問題很難調試定位到具體的位置,往往導致奇怪的bug出現。
    • 能夠幫助理解Qt眾多類的底層不變的邏輯,學起來更容易。

    本文只是對Qt中內存管理進行了梳理,無法涵蓋很多細節問題,讀者需要花一些時間去詳細閱讀Qt助手文檔,最好是寫幾個demo測試驗證。花時間是值得的,因為技術是日新月異的,但是核心的原理變化是不大的。Qt中的內存管理思想和方法,在很多語言、框架中(Python、Objective C、JavaScript等等)都有類似的應用。

    值得一提的是,之所以Qt中具有各種各樣的內存管理方式,是因為它能夠減輕開發者的負擔,更加專注于業務代碼的實現,而不是被內存問題折騰的焦頭爛額。不使用Qt中的內存管理,只用C的手動內存管理仍然可以寫可以運行的代碼!前提是不考慮成本問題,并假設開發者在內存問題上不會犯錯。總之一句話,不要對立各種技術,每種技術都有適用的場景,拋開場景談方法都是不理智的。

    后面會根據需要專門講解一些細節問題,敬請關注!


    本文首發自公眾號“Qt未來工程師”,歡迎關注。

    總結

    以上是生活随笔為你收集整理的【精华】详解Qt中的内存管理机制的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。