Qt 對象樹詳解:從原理到運用

1. 什么是對象樹?

對象樹是一種基于父子關系的對象管理機制。在 Qt 中,所有繼承自 QObject 的類都可以參與到對象樹中。

當一個對象被設置為另一個對象的父對象時,子對象會被添加到父對象的內部列表中,形成一種樹狀結構。

Qt 提供了一個調試工具方法 dumpObjectTree() ,可以幫助開發者打印對象樹的結構。

    QObject* parent = new QObject();QObject* child = new QObject();child->setParent(parent);parent->dumpObjectTree();

1.1 對象樹結構

在 Qt 中,對象樹的每個節點是一個 QObject 對象,而 “邊” 代表父子關系。具體來說:

  • 根節點:沒有父對象的對象(即 parent == nullptr)。
  • 子節點:有父對象的對象,它們會被添加到父對象的 子對象列表 中。
  • 葉子節點:沒有子對象的對象。

grandparent 是根節點,parent 是 grandparent 的子節點,child 是 parent 的子節點,形成了一個三層的樹形結構。

1.2 為什么選擇 new 而不是棧上分配?

如果對象是在棧上創建的(如 局部變量),當作用域結束時,該對象會自動銷毀。可能導致以下問題:

  • 如果子對象比父對象先銷毀,父對象的 子對象列表 可能會指向無效內存。直觀表現可能為:子對象區域顯示為空白狀態。

通過 new 創建對象,可以確保對象的生命周期由對象樹管理,而不是由作用域控制。 這樣可以避免上述問題。

2. 對象樹的工作原理

在 Qt 中,繼承自 QObject 的對象能夠參與到對象樹的前提是 該類必須正確地聲明 Q_OBJECT

對象樹通過以下兩個關鍵成員變量實現:

  • QObject* parent :指向父對象的指針;
  • QList<QObject*> children:存儲子對象的列表。

每個 QObject 對象可以有一個父對象 parent,和有多個子對象 children;子對象會被自動添加到父對象的 children 列表中。

2.1 對象創建時

當創建一個新的 QObject 對象并指定父對象時,會發生:

  1. 新對象的 parent 指針被設置為指定的父對象;
  2. 新對象被自動添加到父對象的 children 列表中。
2.2 對象銷毀時

當一個對象被銷毀時,以下操作會發生:

  1. 該對象的所有子對象會被遞歸銷毀;
  2. 該對象從其父對象的 children 列表中移除。

? Qt 的實現確保了在子對象的析構函數中,它會調用父對象的相關方法(如 QObjectPrivate::removeChild()),將自己從父對象的 children 列表中移除;顯式移除子對象,是為了避免父對象的 children 列表中殘留無效指針,從而確保對象樹的完整性。

class MyObject : public QObject
{Q_OBJECT
public:QString name; // 對象名稱explicit MyObject(QString name, QObject *parent) : name(name), QObject(parent) {}~MyObject(){qDebug() << name << " entering destructor.";if (parent()) {qDebug() << name << " is being removed from parent's children list.";}}
};void CreateObjects()
{MyObject* grandparent = new MyObject(QString("grandparent"));MyObject* parent = new MyObject(QString("parent"), grandparent);MyObject* child = new MyObject(QString("child"), parent);delete grandparent;
}

實際銷毀順序是:child -> parent -> grandparent —— 根據打印信息,child 開始析構時 parent 仍然存在。

3. 對象樹的其它作用

在 Qt 中,對象樹的核心作用是 簡化對象的生命周期管理。通過對象樹,開發者無需過多擔心內存泄露或懸空指針等問題。

“簡化對象的生命周期管理” 在 “工作原理” 部分已經解釋過,除此之外,對象樹的作用還包括:

3.1 層次化組織對象

? 在 GUI 應用程序中,對象之間的關系通常是層次化的。例如,一個窗口可能包含多個控件,而每個控件又可能包含子控件。對象樹提供了一種自然的方式來組織這些對象之間的關系。

int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
  1. Widget 是一個繼承自 QWidget 的類,而 QWidget 又繼承自 QObject。因此 Widget 對象可以參與到 Qt 的對象樹機制中。

  2. 代碼中 w 是一個在棧上分配的對象。

    如果用戶手動關閉窗口(通過點擊窗口的關閉按鈕 或 調用 w.close()),窗口會被隱藏(調用了 hide() 方法),但 w 本身不會被銷毀,因此它的子對象也不會被銷毀 —— 棧上分配的對象由 C++ 的作用域管理,只有當作用域結束時(即 main() 函數返回時),w 才會被銷毀。

  3. 設置 Qt::WA_DeleteOnClose

    Qt::WA_DeleteOnClose 是一個窗口屬性,用于指示窗口在關閉時自動刪除自身。如果設置了該屬性,當用戶手動關閉窗口時,w 會被銷毀,并觸發對象樹的遞歸銷毀。

    Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
    {ui->setupUi(this);// 設置關閉時自動銷毀this->setAttribute(Qt::WA_DeleteOnClose);
    }
    
3.2 其它作用
  • 支持信號與槽機制:

    Qt 的信號與槽機制依賴于對象樹來確保對象的生命周期一致性。

    如果一個對象被銷毀,Qt 會自動斷開與其相關的信號和槽連接;

    這一機制由 QObject 的析構函數觸發,確保信號不會發送到已銷毀的對象。

  • 簡化資源管理:

    通過將資源封裝成 QObject 的子類,并將其添加到對象樹中,可以確保資源在父對象銷毀時被正確釋放。

    class FileWrapper : public QObject
    {Q_OBJECT
    public:FileWrapper(const QString& path, QObject* parent = nullptr): QObject(parent), file(path){}~FileWrapper(){file.close();}
    private:QFile file;
    };QObject* parent = new QObject();
    FileWrapper* file = new FileWrapper("test.txt", parent);
    
  • 提供遍歷和查找功能:通過 QObject::children() 方法,可以獲取某個對象的所有子類對象。

  • 避免重復釋放:對象樹通過集中管理對象的銷毀過程,避免了重復釋放的問題。

4. 注意事項
  • 避免循環引用:如果兩個對象互相設置對方為父對象,會導致循環引用,從而引發內存泄露。

    解決方案: 確保父子關系是單向的。

  • 避免跨線程操作:如果父子對象位于不同的線程中,銷毀順序可能會受到線程調度的影響.

    QThread thread;
    QObject* obj = new QObject();
    obj->moveToThread(&thread);
    QObject* child = new QObject(obj);
    

    如果子對象在父對象銷毀后仍然被訪問(如 調用 child->parent() ),會導致未定義行為。

    解決方案:

    確保父子對象始終位于同一個線程;

    如果需要跨線程操作,可以使用信號與槽機制進行線程間通信,而不是直接操作對象樹。

  • 避免手動干預銷毀:如果手動調用 delete 銷毀一個對象,而該對象同時參與對象樹,可能會導致重復釋放的問題。

    QObject* parent = new QObject();
    QObject* child = new QObject(parent);delete parent; // 自動銷毀 child
    delete child;  // 再次銷毀 child,重復釋放
    

    解決方案:

    讓對象樹完全管理對象的生命周期,避免手動調用 delete ;

    如果必須手動管理對象,確保不將其添加到對象樹中。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72269.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用hutool將json集合對象轉化為對象

集合之間相互轉化 //List轉Json&#xff0c;maps是List類型的參數 String json JSONUtil.toJsonStr(maps); System.out.println("這是json字符串: "json);//Json轉List JSONArray objects JSONUtil.parseArray(json); List<Map> maps1 JSONUtil.toList(objec…

Qt關于平滑滾動的使用QScroller及QScrollerProperties類說明

一、觸控時代的滾動工具&#xff1a;QScroller類設計介紹 1.1 從機械滾輪到數字慣性 在觸控設備普及前&#xff0c;滾動操作如同老式打字機的滾軸&#xff0c;只能通過鼠標滾輪或滾動條進行離散式控制。QScroller的出現如同給數字界面裝上了"慣性飛輪"&#xff0c;…

JavaAPI(網絡編程)

網絡通信協議 通信協議 ?所謂通信協議&#xff0c;是指通信雙方在進行數據交換時必須遵守的規則和約定。?這些規則確保了雙方能夠有效地進行通信&#xff0c;實現信息的交換和資源共享。通信協議定義了傳輸時的數據格式、控制信息以及傳輸順序和速度等&#xff0c;確保雙方…

Java---入門基礎篇(下)---方法與數組

前言 本篇文章主要講解有關方法與數組的知識點 ,是基礎篇的一部分 , 而在下一篇文章我會講解類和對象的知識點 入門基礎篇上的鏈接給大家放在下面啦 ! Java---入門基礎篇(上)-CSDN博客 感謝大家點贊&#x1f44d;&#x1f3fb;收藏?評論?&#x1f3fb; 歡迎各位大佬指點…

Python 爬蟲 – BeautifulSoup

Python 爬蟲&#xff08;Web Scraping&#xff09;是指通過編寫 Python 程序從互聯網上自動提取信息的過程。 爬蟲的基本流程通常包括發送 HTTP 請求獲取網頁內容、解析網頁并提取數據&#xff0c;然后存儲數據。 Python 的豐富生態使其成為開發爬蟲的熱門語言&#xff0c;特…

圖像分類項目1:基于卷積神經網絡的動物圖像分類

一、選題背景及動機 在現代社會中&#xff0c;圖像分類是計算機視覺領域的一個重要任務。動物圖像分類具有廣泛的應用&#xff0c;例如生態學研究、動物保護、農業監測等。通過對動物圖像進行自動分類&#xff0c;可以幫助人們更好地了解動物種類、數量和分布情況&#xff0c;…

物聯網 智慧園區井蓋管理辦法和功能介紹

在園區內實現 智慧井蓋 的定位、內部氣體檢測和紅外監測等頂級功能&#xff0c;可以顯著提升園區的安全管理水平和運維效率。以下是智慧井蓋系統的詳細設計方案和功能實現&#xff1a; 一、系統架構 智慧井蓋系統可以分為以下層次&#xff1a; 1. 感知層 定位模塊&#xff1…

零基礎deep seek+剪映,如何制作高品質的視頻短片

以下是專為零基礎學習者設計的 剪映專業版詳細教程&#xff0b;Deep seek配合制 &#xff0c;包含從入門到精通的系統化教學&#xff0c;配合具體操作步驟與實用技巧&#xff1a; 基于DeepSeek與剪映協同制作高品質視頻短片的專業流程指南&#xff08;2025年最新實踐版&#x…

PHP:IDEA開發工具配置XDebug,斷點調試

文章目錄 一、php.ini配置二、IDEA配置 一、php.ini配置 [xdebug] zend_extension"F:\wamp64\bin\php\php7.4.0\ext\php_xdebug-2.8.0-7.4-vc15-x86_64.dll" xdebug.remote_enable on xdebug.remote_host 127.0.0.1 xdebug.remote_port 9001 xdebug.idekey"…

改進YOLOv8模型的空間注意力機制研究:RFAConv的貢獻與實現

文章目錄 1. 背景介紹2. 什么是RFAConv?3. YOLOv8中的RFAConv實現3.1 RFAConv模塊設計3.2 在YOLOv8中集成RFAConv4. 性能對比與實驗結果4.1 實驗設置4.2 實驗結果5. 模型優化與調優5.1 調整RFAConv模塊的超參數5.2 數據增強策略5.3 更深層的注意力機制5.4 混合卷積與優化計算圖…

【Java】使用jdk自帶的zip壓縮實現任意文件壓縮打包下載功能(復制即用)

前言 在實際項目中&#xff0c;我們可能會接到將文件或者資料打包壓縮導出的需求&#xff0c;例如將系統中某些生成的文件一起打包壓縮下載提供給客戶使用&#xff0c;今天提供一個jdk自帶的工具類快速實現打包壓縮的功能&#xff0c;方法我已經封裝好&#xff0c;大家如果在項…

騰訊云擴容記錄

騰訊云擴容&#xff1a; sudo yum install -y cloud-utils-growpart 安裝擴容工具 sudo file -s /dev/vda1 有數據 sudo LC_ALLen_US.UTF-8 growpart /dev/vda 1 sudo resize2fs /dev/vda1 df -Th 完畢 以下是對執行的命令的詳細解釋以及背后的原理&#xff1a; 1. 安裝 cloud…

服務流程設計和服務或端口重定向及其websocket等應用示例

服務流程設計和服務或端口重定向及其websocket等應用示例 目錄 服務或端口重定向的服務設計和websocket等應用示例 一、通用請求控制流程 1.1、入口 1.2、所有GET請求首先預檢控制單元 1.3、http請求會分別自動307重定向 1.4、所有請求首先執行跨源控制單元 1.5、然后…

PHP面試題--后端部分

本文章持續更新內容 之前沒來得及整理時間問題導致每次都得找和重新背 這次整理下也方便各位小伙伴一起更輕松的一起踏入編程之路 歡迎各位關注博主不定期更新各種高質量內容適合小白及其初級水平同學一起學習 一起成為大佬 數組函數有那些 ps&#xff1a;本題挑難的背因為…

深入了解 MySQL 中的 JSON_CONTAINS

深入了解 MySQL 中的 JSON_CONTAINS MySQL 5.7 及更高版本引入了對 JSON 數據類型的支持&#xff0c;使得在數據庫中存儲和查詢 JSON 數據成為可能。在這些新功能中&#xff0c;JSON_CONTAINS 函數是一個非常有用的工具&#xff0c;允許我們檢查一個 JSON 文檔是否包含特定的值…

git命令學習記錄

1. git reset 參數說明 git reset 是用來回退版本的&#xff0c;它可以添加三個參數&#xff0c;常用的使用格式是這樣的&#xff1a;git reset [--hard | --soft | --mixed] 版本號 一般使用git修改文件并提交需要三步&#xff0c;第一步在文本編輯器中編輯文件&#xff0c;也…

使用DeepSeek+KIMI生成高質量PPT

一、使用DeepSeek DeepSeek官網&#xff1a;DeepSeek 點擊“開始對話”&#xff0c;進入交互頁面。 在上圖中&#xff0c;輸入問題&#xff0c;即可獲取AI生成的結果。 基礎模型&#xff08;V3&#xff09;&#xff1a;通用模型&#xff08;2024.12&#xff09;&#xff0c;高…

深度學習工程師的技術圖譜和學習路徑

在構建一個深度學習工程師的技術圖譜時,按照“技能樹與能力模型”的結構可以幫助清晰地展示出技術體系的層次化關系,幫助學習者更好地理解每個技術點的依賴與順序。 深度學習工程師的技術圖譜和學習路徑 以下是深度學習工程師的技能樹,包括從基礎到進階的學習路徑,以及對…

RabbitMQ系列(五)基本概念之Queue

在 RabbitMQ 中&#xff0c;Queue&#xff08;隊列&#xff09; 是存儲消息的容器&#xff0c;也是消息傳遞的核心載體。以下是其核心特性與作用的全方位解析&#xff1a; 一、Queue 的定義與核心作用 消息存儲容器 Queue 是 RabbitMQ 中實際存儲消息的實體&#xff0c;生產者…

MySQL—使用binlog日志恢復數據

一、binlog日志恢復數據簡介 在 MySQL 中&#xff0c;使用二進制日志&#xff08;binlog&#xff09;恢復數據是一種常見的用于故障恢復或數據找回的方法。以下是詳細的使用步驟&#xff1a; 確認 binlog 已啟用&#xff1a;首先需要確認 MySQL 服務器已經啟用了二進制日志功…