條款9:利用destructors避免泄露資源

對指針說拜拜。承認吧,你從未真正喜歡過它,對不?

好,你不需要對所有指針說拜拜,但是你真的得對那些用來操控局部性資源(local resources)的指針說莎唷娜拉了。

舉個例子,你正在為“小動物收養保護中心”(一個專門為小狗小貓尋找收養家庭的組織)編寫一個軟件。收養中心每天都會產生一個文件,其中有它所安排的當天收養個案。你的工作就是寫一個程序,讀這些文件,然后為每一個收養個案做適當處理。


合理的想法是定義一個抽象基類(abstract base class)ALA("Adorable Litle Animal"),再從中派生出針對小狗和小貓的具體類(concrete classes)。其中有個虛函數processAdoption,負責“因動物種類而異”的必要處理動作。

class ALA{
public:
virtual void processAdoption()= 0;
...
};class Puppy: public ALA
{
public:
virtual ?void processAdoption();
...
};class Kitten: public ALA
{
public:
virtual void processAdoption ();
...
};

你需要一個函數,讀取文件內容,并視文件內容產生一個Puppyobject或一個Kitten object。這個任務非常適合用virtual constructor完成,那是條款25中描述的一種函數。

對本目的而言,以下聲明便是我們所需要的:

//從s讀取動物信息,然后返回一個指針,指向一個
// 新分配的對象,有著適當的類型(Puppy或Kitten)
ALA * readALA(istream& s);

你的程序核心大約是一個類似這樣的函數:

void processAdoptions(istream& dataSource)
{while (datasource)//如果還有數據,{ALA* pa = readALA(dataSource);//取出下一只動物,pa->processAdoption();//處理收養事宜,delete pa;//刪除readALA返回的對象。}
}

這個函數走遍dataSource,處理它所獲得的每一條信息。

唯一需要特別謹慎的是,它必須在每次迭代的最后,記得將pa刪除。這是必要的,因為每當readALA被調用,便產生一個新的 heap object。如果沒有調用 delete,這個循環很快便會出現資源泄漏的問題。

現在請考慮:如果pa->processAdoption拋出一個exception,會發生什么事情。processAdoptions 無法捕捉它,所以這個 exception 會傳播到 processAdoptions的調用端。processAdoptions 函數內“位于pa->processAdoption 之后的所有語句”都會被跳過,不再執行,這也意味pa不會被刪除。結果呢,只要pa->processAdoption 拋出一個 exception,processAdoptions 便發生一次資源泄漏。

要避免這一點,很簡單:

void processAdoptions(istream& dataSource)
{while (dataSource){ALA* pa = readALA(dataSource),try {pa->processAdoption();}catch (...)//捕捉所有的exceptions。{delete pa;//當exception 被拋出,避免資源泄漏。throw;//將exception 傳播給調用端。}delete pa;//如果沒有exception被拋出,也要避免資源泄漏。}
}

但你的程序因而被try 語句塊和catch 語句塊搞得亂七八糟。

更重要的是,你被迫重復撰寫其實可被正常路線和異常路線共享的清理代碼(cleanup code)本例指的是delete動作。

這對程序的維護造成困擾,撰寫時很煩人,感覺也不理想。不論我們是以正常方式或異常方式(拋出一個exception)離開processAdoptions函數,我們都需要刪除pa,那么何不集中于一處做這件事情呢?

其實不必大費周章,只要我們能夠將“一定得執行的清理代碼”移到processAdoptions函數的某個局部對象的destructor 內即可。因為局部對象總是會在函數結束時被析構,不論函數如何結束(唯一例外是你調用longjmp而結束。longjmp 的這個缺點正是C++ 支持exceptions的最初的主要原因)。于是,我們真正感興趣的是,如何把delete 動作從 processAdoptions 函數移到函數內某個局部對象的destructor內。

解決辦法就是,以一個“類似指針的對象”取代指針pa,如此一來,當這個類似指針的對象被(自動)銷毀,我們可以令其destructor 調用delete。

“行為類似指針(但動作更多)”的對象我們稱為smart pointers。如條款28所言,你可以做出非常靈巧的“指針類似物”。本例倒是不需要什么特別高檔的產品,我們只要求它在被銷毀(由于即將離開其scope)之前刪除它所指的對象,就可以啦。

技術上這并不困難,我們甚至不需要自己動手。C++標準程序庫(見條款E49)提供了一個名為auto_ptr的class template,其行為正是我們所需要的。每個auto_ptr的constructor 都要求獲得一個指向 heap object 的指針;其destructoy會將該heapobject 刪除。

如果只顯示這些基本功能,auto_ptr 看起來像這樣:

template<class T>
class auto_ptr
{
public:auto_ptr(T* p = 0) :ptr(p) {}// 存儲對象。~auto ptr(){delete ptr;// 刪除對象。}private:T* ptr;//原始指針(指向對象).
};

auto_ptr 標準版遠比上述復雜得多。上述這個剝掉一層皮的東西并不適合實際運用2(至少還需加上copy constructor,assignment operator 及條款28所討論的指針仿真函數operator*和operator->),但是其背后的觀念應該很清楚了:以auto_ptr 對象取代原始指針,就不需再擔心heap objects沒有被刪除一一即使是在exceptions被拋出的情況下。

注意,由于auto ptr destructor 采用“單一對象”形式的delete,所以auto ptr 不適合取代(或說包裝)數組對象的指針。如果你希望有一個類似 autoptr的template可用于數組身上,你得自己動手寫一個。不過如果真是這樣,或許更好的選擇是以vector 取代數組。
以auto_ptr對象取代原始指針之后,processAdoptions 看起來像這樣:

void processAdoptions(istream& dataSource)
{while (dataSource){auto_ptr<ALA> pa(readALA(dataSource)pa->processAdoption();}
}

這一版和原先版本的差異只有兩處。

  • 第一,pa被聲明為一個 auto_ptr<ALA>對象,不再是原始的 ALA*指針;
  • 第二,循環最后不再有delete 語句。就這樣啦,其他每樣東西都沒變,除了析構動作外,auto_ptr 對象的行為和正常指針完全一樣。很簡單,不是嗎?

隱藏在auto ptr背后的觀念一—以一個對象存放“必須自動釋放的資源”,并依賴該對象的destructor 釋放一—亦可對“以指針為本”以外的資源施行。

考慮圖形界面(GUT)應用軟件中的某個函數,它必須產生一個窗口以顯示某些信息:

//此函數可能會在拋出一個 exception 之后發生資源泄漏問題。
void displayInfo(const Informations info)
{WINDOW_HANDLE w(createwindow());displayinfo in window corresponding to w,destroyWindow(w);
}

許多窗口系統都有 C語言接口,運行諸如 createWindow 和 destroyWindow這類函數,取得或釋放窗口(被視為一種資源)。如果在信息顯示于的過程中發生exception,w所持有的那個窗口將會遺失,其他動態分配的任何資源也會遺失。

解決之道和先前一樣,設計一個class,令其constructor 和destructor 分別取得資源和釋放資源:

//這個class 用來取得及釋放一個 window handle。
class WindowHandle {
public:WindowHandle(WINDOW HANDLE handle) :w(handle) {}~WindowHandle() {destroyWindow(w);}operator WINDOW_HANDLE(){return w;//詳述于下。}
private:WINDOW_HANDLE w;//以下函數被聲明為private,用以阻止產生多個 WINDOW HANDLE。//條款28討論了一個更彈性的做法。WindowHandle(const WindowHandle&);WindowHandle& operator=(const WindowHandle&);
};

這看起來就像auto_ptr template 一樣,只不過其賦值動作(assignment)和復制動作(copying)被明確禁止了(見條款E27)。

此外,它有一個隱式類型轉換操作符,可用來將一個 windowHandle 轉換為一個 WINDOW HANDLE。

這項能力對于WindowHandleobject 的實用性甚有必要,意味你可以像在任何地方正常使用原始的WINDOW_HANDLE一樣使用一個 windowHandle(不過條款5也告訴你為什么應該特別小心隱式類型轉換操作符)。
?


有了這個 WindowHandle class,我們可以重寫 displayInfo如下:

// 此函數可以在exception 發生時避免出現資源泄漏問題。
void displayInfo(const Information& info)
{WindowHandle w(createwindow());display info in window corresponding to w;
}

現在即使 displayInfo函數內拋出 exception,createWindow 所產生的窗口還是會被銷毀。

只要堅持這個規則,把資源封裝在對象內,通常便可以在exceptions 出現時避免泄漏資源。

但如果exception 是在你正取得資源的過程中拋出的,例如在一個“正在抓取資源”的class constructor 內,會發生什么事呢?

如果exception 是在此類資源的自動析構過程中拋出的,又會發生什么事呢?此情況下constructors和destructors 是否需要特殊設計?

是的,它們需要,你可以在條款 10和條款 11中學到這些技術。
?

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

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

相關文章

Flutter 中的 CircularProgressIndicator 小部件:全面指南

Flutter 中的 CircularProgressIndicator 小部件&#xff1a;全面指南 在 Flutter 應用開發中&#xff0c;加載指示器是提供用戶反饋的重要組成部分&#xff0c;特別是在需要等待數據加載的場景中。CircularProgressIndicator 是 Flutter 提供的一個表現圓形加載動畫的小部件。…

Python進階:探索Python標準庫和第三方庫

在前兩篇文章中,我們介紹了Python的基本語法和面向對象編程。在這篇文章中,我們將深入探索Python的標準庫以及一些常用的第三方庫。Python的強大之處不僅在于其簡潔的語法,還在于豐富的庫生態系統。通過使用這些庫,你可以更高效地完成各種任務,從文件操作到數據分析、網絡…

godot4.2 + GDextension c++在 vs code 中斷點調試配置

游戲開發中如果做不到自己編寫的代碼做斷點調試&#xff0c;無不是瞎子摸象&#xff0c;特別是C這么底層的語言。這2天開始在VS studio中折騰&#xff0c;一直折騰不出結果&#xff0c;幾次想要放棄GODOT。最終今天在VS code中搞定了這斷點調試C代碼。 在上一篇文章我已經做好了…

React hooks - useContext

useContext 用法使用以非侵入的方式使用 Context使用 useContext 重構 useReducer 案例 用法 實現多層組件的數據傳遞 在全局創建 Context 對象在父組件中使用 Context.Provider 提供數據在子組件中使用 useContext 使用數據 import React, { useContext } from react // 全局…

全網爆火Remini 粘土濾鏡風格,我用ComfyUI一鍵生成了(保姆級教程)!

一、火爆全網的Remini&#xff01; Remini真的火爆了&#xff01;最近大家的朋友應該都被粘土濾鏡刷屏了。 小紅書上粘土濾鏡、粘土特效的帖子動不動就是幾百萬瀏覽量&#xff0c;幾千贊。 在有些電商平臺上還有人接單&#xff0c;幫忙定制remini粘土風格的照片&#xff01; …

解釋瀏覽器緩存和本地存儲的區別,以及如何實現事件的防抖和節流

1:瀏覽器緩存和本地存儲的區別: 瀏覽器緩存:瀏覽器緩存是一種臨時性的數據存儲,用于提高網頁加載速度。緩存的數據存儲在內存或磁盤中,當用戶再次訪問相同的資源時,瀏覽器會優先從緩存中讀取數據,而不是從服務器重新請求。緩存的數據可以在瀏覽器關閉后自動清除,或者根據 HTT…

vue+three.js實現3d系統的搭建

1.首先node.js是12.22版本的&#xff0c;安裝three.js可以參考這篇文章 直接用Threejs入門-安裝教程_安裝three.js-CSDN博客 直接在終端安裝three.js即可 npm install --save three 在相同目錄下安裝vite構建工具 npm install --save-dev vite 在項目里面看package.json中…

神秘顧客調查:第三方渠道監測如何操作?

第三方渠道監測是指通過專業的第三方機構或服務商&#xff0c;對企業的銷售渠道進行系統化的監督和評估。這種監測幫助企業了解各渠道的表現&#xff0c;確保合規性&#xff0c;提升市場競爭力。深圳神秘顧客市場調查限公司&#xff08;SMS&#xff09;總結了第三方渠道監測的操…

【安裝筆記-20240528-Linux-在 Vultr 云服務器上安裝 OpenWRT】

安裝筆記-系列文章目錄 安裝筆記-20240528-Linux-在 Vultr 云服務器上安裝測試 OpenWRT 文章目錄 安裝筆記-系列文章目錄安裝筆記-20240528-Linux-在 Vultr 云服務器上安裝測試 OpenWRT 前言一、軟件介紹名稱&#xff1a;OpenWRT主頁官方介紹 二、安裝步驟測試版本&#xff1a…

多function-calling 調用

多function-calling 調用 接上一篇function-calling調用&#xff0c;本篇實現了一個多function-calling的調用。OpenAI會根據function的描述自己來判斷應該調用哪個function。最終調用function的動作是由我們來決定的&#xff0c;當然你也可以不調對應的函數。 兩個函數分別是…

案例研究|MeterSphere助力萬物云構建高效自動化測試平臺

萬物云空間科技服務股份有限公司&#xff08;以下簡稱為“萬物云”&#xff09;&#xff0c;前身為萬科物業發展股份有限公司&#xff0c;是國內領先的物管龍頭上市公司。作為一家科技引領的全域空間服務商&#xff0c;萬物云致力于打造產業級共享服務平臺&#xff0c;基于空間…

1. lambda初體驗

首先聲明一個函數式接口&#xff0c;就只接口內只有一個抽象方法 //函數式接口 public interface Factory {Object getObject();}接口實現類 public class SubClass implements Factory {Overridepublic Object getObject() {return new User();}}User類 public class User …

酒店提前線上訂房小程序源碼系統 PHP+MySQL組合開發 源碼開源可二開 帶完整的安裝代碼包以及搭建教程

系統概述 隨著移動互聯網的普及&#xff0c;越來越多的人習慣通過手機進行酒店預訂。傳統的線下訂房方式逐漸無法滿足用戶的需求&#xff0c;酒店提前線上訂房小程序的出現成為必然趨勢。該源碼系統的開發旨在為酒店提供一個便捷、高效的線上訂房平臺&#xff0c;提升用戶體驗…

基于微信小程序+ JAVA后端實現的【醫院掛號預約系統】 設計與實現 (內附設計LW + PPT+ 源碼+ 演示視頻 下載)

項目名稱 項目名稱&#xff1a; 《基于微信小程序的醫院掛號預約系統設計與實現》 項目技術棧 該項目采用了以下核心技術棧&#xff1a; 后端框架/庫&#xff1a; Java, SSM框架數據庫&#xff1a; MySQL前端技術&#xff1a; 微信小程序, uni-app 項目展示 全文概括 本…

設置單群聊消息擴展

根據消息 ID &#xff0c;對單聊會話或群聊會話中已經發送的消息設置擴展信息。每次最多可以設置 100 個擴展屬性信息&#xff0c;最多可設置 300 個。 通過 Server API 操作消息擴展&#xff0c;默認不會向操作者的客戶端同步&#xff0c;會導致擴展信息不一致。如有需要&…

MySQL觸發器實戰:自動執行的秘密

歡迎來到我的博客&#xff0c;代碼的世界里&#xff0c;每一行都是一個故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交給時間 &#x1f3e0; &#xff1a;小破站 MySQL觸發器實戰&#xff1a;自動執行的秘密 前言觸發器的定義和作用觸發器的定義和作用觸發器的…

大模型領域新聞跟蹤

楊值麟 月之暗面楊植麟&#xff1a;大模型開發是“承包森林”月之暗面集結最強創投&#xff0c;“清華師姐”是最強“助攻”月之暗面楊植麟&#xff1a;互聯網研發是“種樹”&#xff0c;大模型研發是“承包森林”月之暗面楊植麟復盤大模型創業這一年&#xff1a;向延綿而未知…

搜維爾科技:穿上Xsens Link動作捕捉套裝,進行精準的運動捕捉

穿上Xsens Link動作捕捉套裝&#xff0c;進行精準的運動捕捉 搜維爾科技&#xff1a;穿上Xsens Link動作捕捉套裝&#xff0c;進行精準的運動捕捉

vue 筆記01

目錄 01 vuejs中屬性的基本使用 02 v-show指令的使用 03 v-if 指令的使用 04 v-for指令的使用 05 v-model 指令 06 template模板標簽 07 v-on事件的綁定指令 08 事件中的event對象 01 vuejs中屬性的基本使用 {{ }} 叫做mustache模板語法 雙花括號 小胡子語法 雙花括號…

安卓手機APP開發__構建通話應用

安卓手機APP開發__構建通話應用 目錄 概述 依賴項和權限 注冊應用 平臺集成 注冊通話 添加通話 接聽來電 拒接來電 去電 將通話置于保持狀態 斷開連接 轉接音頻 前臺支持 Surface 支持 概述 使用 Telecom Jetpack 庫為用戶提供最佳視頻和音頻體驗。借助 Teleco…