Qt的記憶體管理機制

来源:https://www.cnblogs.com/apocelipes/archive/2018/11/20/9991845.html
-Advertisement-
Play Games

當我們在使用Qt時不可避免得需要接觸到記憶體的分配和使用,即使是在使用Python,Golang這種帶有自動垃圾回收器(GC)的語言時我們仍然需要對Qt的記憶體管理機制有所瞭解,以更加清楚的認識Qt對象的生命周期併在適當的時機加以控制或者避免進入陷阱。 這篇文章里我們將學習QObject & paren ...


當我們在使用Qt時不可避免得需要接觸到記憶體的分配和使用,即使是在使用Python,Golang這種帶有自動垃圾回收器(GC)的語言時我們仍然需要對Qt的記憶體管理機制有所瞭解,以更加清楚的認識Qt對象的生命周期併在適當的時機加以控制或者避免進入陷阱。

這篇文章里我們將學習QObject & parent對象管理機制,以及QWidget與記憶體管理這兩點Qt的基礎知識。

QObject和記憶體管理

在Qt中,我們可以大致把對象分為兩類,一類是QObject和它的派生類;另一類則是普通的C++類。

對於第二種對象,它的生命周期與管理和普通的C++類基本沒有區別,而QObject和它的派生類則有以下的顯著區別:

  • QObject和其派生類可以使用SIGNAL/SLOT機制
  • 它們一般會有一個parent父對象的指針,用於記憶體管理(後面重點說明)
  • 對於QWidget和其派生類來說,記憶體管理要稍微複雜一些,因為QWidget需要和eventloop高度配合才能工作(後面也會重點說明)

signal和slot一般來說並不會對記憶體管理產生影響,但是對close()槽的處理會對QWidget產生一些影響,所以我們放在後面講解。

那麼先來看一下QObject和parent機制。

QObject的parent

我們時常能看到QWidget或者其他的控制項的構造函數中有一項參數parent,預設值都為NULL,例如:

QLineEdit(const QString &contents, QWidget *parent = nullptr);
QWidget(QWidget *parent = nullptr, Qt::WindowFlags f = ...);

這個parent的作用就在於使當前的對象實例加入parent指定的QObject及其派生類的children中,當一個QObject被delete或者調用了它的析構函數時,所有加入的children也會全部被析構。

如果parent設置為NULL,會有如下的情況:

  • 如果是構造時直接指定了NULL,那麼當前實例不會有父對象存在,Qt也不能自動析構該實例除非實例超出作用域導致析構函數被調用,或者用戶在恰當的實際使用delete操作符或者使用deleteLater方法;
  • 如果已經指定了非NULL的parent,這時將它設置成了NULL,那麼當前實例會從父對象的children中刪除,不再受到QObject & parent機制的影響;
  • 對於QWidgetparent為NULL時代表其為一個頂層視窗,也可以就是獨立於其他widget在系統任務欄單獨出現的widget,對於永遠都是頂層視窗的widget,例如QDialog,當parent不為NULL時他會顯示在父widget中心區域的上層;
  • 如果QWidgetparent為NULL或是其他值,在其加入佈局管理器或者QMainWindow設置widget時,會自動將parent設置為相應的父widget,在父控制項銷毀時這些子控制項以及佈局管理器對象會一併銷毀。

所以我們可以看出,QObject對象實際上擁有一顆類實例關係樹,在樹中保存了所有通過指定parent註冊的子對象,而子對象里又保存有其子對象的關係樹,所以當一個父對象被銷毀時,所有依賴或間接依賴於它的對象都會被正確的釋放,使用者無需手動管理這些資源的釋放操作。

基於此原理,我們可以放心的讓Qt管理資源,這裡有幾個建議:

  1. 對於QObject及其派生類,如果彼此之間存在一定聯繫,則應該儘量指定parent,對於QWidget應該指定parent或者加入佈局管理器由管理器自動設置parent。
  2. 對象只需要在局部作用域存在時可以選擇不進行記憶體分配,利用局部作用域變數的生命周期自動清理資源。
  3. 對於非QWidget的對象來說,如果不指定非NULLparent,則需要自己管理對象資源。QWidget比較特殊,我們在下一節講解。
  4. 對於在局部作用域上創建的父對象及其子對象,要註意對象銷毀的順序,因為父對象銷毀時也會銷毀子對象,當子對象會在父對象之後被銷毀時會引發double free。

QWidget和記憶體的釋放

QWidget也是QObject的子類,所以在parent機制上是沒有區別的,然而實際使用時我們更多的是使用“關閉”(close)而不是delete去刪除控制項,所以差異就出現了。

先提一下widget關閉的流程,首先用戶觸發close()槽,然後Qt向widget發送QCloseEvent,預設的QCloseEvent會做如下處理:

  1. 將widget隱藏,也就是hide()
  2. 如果有設置Qt::WA_DeleteOnClose,那麼會接著調用widget的析構函數

我們可以看到,widget的關閉實際是將其隱藏,而沒有釋放記憶體,雖然我們有時會重寫closeEvent但也不會手動釋放widget。

看一個因為close機制導致的記憶體泄漏的例子,我們在button被單擊後彈出某個自定義對話框:

button.ConnectClicked(func (_ bool) {
  dialog := NewMyDialog()
  dialog.Exec()
})

因為dialog在close時會被隱藏,而且沒有設置DeleteOnClose,所以Qt不會去釋放dialog,而用戶也無法回收dialog的資源,也行你會說golang的GC不是能處理這種情況嗎,然而遺憾的是GC並不能處理cgo分配的資源,所以如果你期望GC做善後的話恐怕要失望了,每次點擊按鈕後記憶體用量都會增加一點,沒錯,記憶體泄露了。

那麼給dialog設置一個parent,像這樣,會如何呢?

dialog.SetParent(self)

遺憾的是,並沒有什麼區別,因為這樣只是把dialog加入父控制項的children,並沒有刪除dialog,只有父對象被銷毀時記憶體才會真正釋放。

解決辦法也有三個。

第一種是使用deleteLater,例如:

dialog.DeleteLater()

這會通知Qt的eventloop在下次進入主迴圈的時候析構dialog,這樣一來確實解決了記憶體泄露,不過缺點是會有不可預測的延遲存在,有時候延遲是難以接受的。

第二種是手動刪除widget,適用於parent為NULL的場合:
C++:

delete dialog;

golang:

dialog.DestroyMyDialog()

說明一下,DestroyType也是qtmoc生產的幫助函數,因為golang沒有析構函數的概念,所以goqt使用生成的該幫助函數顯示調用底層C++對象的析構函數。

第三種比較簡單,對於單純顯示而不需要和父控制項做交互的widget,直接設置DeleteOnClose即可,close時widget會被自動析構。

當然對於PyQt5來說並不會存在如上的問題,sip庫能很好的與python的GC一起工作。唯一需要註意的是有時底層C++對象已經被釋放,但是上層python對象依然存在,這時使用該對象將導致拋錯。

總結

Qt提供了一套方便的機制幫助我們進行記憶體和資源管理,使我們從繁重的勞動中得到了部分的解放,但同時也要註意到那些很容易坑,這樣才能寫出健壯的正確執行的程式。

如有錯誤之處,歡迎批評指正。

參考:

http://doc.qt.io/qt-5/qwidget.html

http://doc.qt.io/qt-5/qobject.html

http://doc.qt.io/qt-5/objecttrees.html

https://stackoverflow.com/questions/20164015/is-deletelater-necessary-in-pyqt-pyside


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前提: 微信測試號,用微信開發者工具測試 二、簡單覆述文檔: 1、引入JS文件 在需要調用JS介面的頁面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js 如需進一步提升服務穩定性,當上述資源不可訪問時,可改訪問:ht ...
  • 啦啦啦,小白福音來啦!零基礎的前端開發初學者看過來!這一波良心推薦的【Web前端學習路線】乾貨,不談虛的,直接來談每個階段要學習的內容 首先,給大家分享一張以 企業崗位需求為導向 Web前端技能點圖 我還是要推薦下我自己創建的web前端資料分享群606721798,這是web前端學習交流的地方,不管 ...
  • webSocket是什麼 webSocket是HTML5新出的一種協議,底層是基於TCP/IP協議的。跟http沒有關係,只是復用了http握手通道,用來升級協議。 webSocket的作用 輪詢:客戶端以一定的時間間隔向服務端發出請求,以頻繁請求的方式來保持客戶端和伺服器端的同步。缺點: 瀏覽器需 ...
  • 概述 1、瞭解springboot的作用 2、構建第一個springboot項目 一、springboot的作用 ① 原先在構建SSM項目的時候,可以感覺到,在一些不是很大的項目,構建配置文件的過程所需的時間花費甚至超過項目本身的業務,每次構建一個新項目都得搞各種配置文件(xml\configura ...
  • 前面兩篇文章,寫了python線程同步原語的基本應用。下麵這篇文章主要是通過閱讀源碼來瞭解這幾個類的內部原理和是怎麼協同一起工作來實現python多線程的。 相關文章鏈接:python同步原語--線程鎖 python--線程同步原語 一、關於Condition類 Condition的用法: 用來記錄 ...
  • 71.jsp有哪些內置對象?作用分別是什麼? 72.jsp有哪些動作?作用分別是什麼? 73.JSP中動態INCLUDE與靜態INCLUDE的區別? 74.說一說Servlet的生命周期? 75.說說JSP 的生命周期? ...
  • 問題 已知某應力張量的分量為 $$ \sigma_{11}=3,\quad\sigma_{12} = \sigma_{13} = 1, \quad \sigma_{22} = \sigma_{33} = 0, \quad\sigma_{23} = 2 $$ 求 1、全部主應力 2、最大主應力對應的主 ...
  • 多線程 等待一次性事件 future概念 背景:有時候,一個線程只等待另一個線程一次,而且需要它等待的線程的返回值。 案例:滴滴叫車時,點完了叫車按鈕後,叫車的後臺線程就啟動了,去通知周圍的計程車。這時,用戶就可以去乾別的了,而且用戶只等待叫車的線程一次就夠了,也就是有計程車應答了,這個等待就結束了 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...