之前已經大概介紹過 C++ Guidelines Support Library 這個簡單的函式庫了;當時,也有簡單地介紹一下裡面的 owner<T>。
而這一篇,則是來講一下一個 Heresy 個人覺得還滿實用的東西:finally。
當在寫程式的時候,常常會在某個地方,需要建立很多臨時性的資源、來做計算、儲存,而當結果計算出來後,這些東西就需要被釋放掉。
如果程式的路線很簡單,只有一條線的話,其實很好處理、不太會成為問題;但是如果是一個函式,可能會有好幾條路線、甚至會提早 return、結束函式的話,要怎麼比較好地去把這些臨時性的資源清理掉,就會是個問題了。
例如下面的程式:
int leak( int iInput) { CData* pData = new CData(); if (iInput > 0) return 1; delete pData; return -1; }
這個函式輸入的 iInput 如果大於 0 的話,就會回傳 1,否則就會回傳 -1。
而裡面的 pData 只是示意性的臨時資料,實際上沒有用到。
這邊可以看到,如果是在回傳 -1 的狀況下,pData 會被正確地釋放,不會有 memory leak 的問題;但是如果走到回傳 1 的狀況,則會因為沒有刪除 pData,所以會有 memory leak 的問題。
當然,以這個過分簡化的例子來說,其實有很多解法可以解決這個問題。但是,在實際更為複雜的狀況下,其實要很好、很簡單解決這樣的問題,其實還是滿麻煩的…
比如說:
-
雖然這邊可以使用 Smart Pointer 來做資源管理、甚至是透過避免使用指標的方法,來避免 memory leak;但是有的時候,並不是簡單的 delete 就可以了,還有很多步驟要做(比如說 fclose()),這時候就沒辦法透過這樣的方法來解決。
-
雖然也可以很認真地在每個 return 之前,都加上資源釋放的程式碼;但是實際上,如果可能的路徑很複雜、在每個階段都可能會需要 return 的話,這樣的寫法就很容易有遺漏、出問題。
而如果是使用 GSL 的 finally 的話,在某些狀況下是可能可以更簡單地解決這樣的問題的。
finally 這個函式是定義在 <gsl/gsl_util> 這個 header 檔裡面,呼叫他的時候必須要給他一個可呼叫的物件,他會產生一個 final_action<T> 的物件、然後在這個物件消失的時候,就會去執行所傳入的可呼叫物件。
以上面的例子來說,使用 finally 的修改方法可以寫成:
int noleak(int iInput) { CData* pData = new CData(); auto _ = gsl::finally([pData] { delete pData; }); if (iInput > 0) return 1; return -1; }
可以看到,這邊在建立了 pData 後,就去呼叫 GSL 的 finally,並傳一個 pData 指向的記憶體空間刪除掉。
另外,這邊則是用一個物件「_」去接 finally 所回傳的 final_action<T> 物件。
之後,不管是哪邊要 return、離開這個函式的時候,物件「_」都會再被釋放的時候,去執行前面賦予他的 lambda function、也就是刪除 pData 指向的記憶體空間;所以不管是 return 1 或是 return -1 的狀況,pData 的資料都會在離開函式前,被「_」刪除,所以也就不會有 memory leak 了~
而如果是比較複雜的資源釋放步驟,在這邊也都可以解決;而由於他會在離開函式的時候被自動執行,所以也不用擔心新加入的 return 路徑忘了去釋放資源~
個人目前還沒真的有用上這個東西,不過感覺上應該還是會有要用到的機會的~所以這邊就先記錄一下吧。
另外,雖然還沒試過,不過他應該也可以取代 atexit(),來做到類似的效果。
完整的範例:https://github.com/KHeresy/misc/blob/master/gsl_finally.cpp
參考: