用RAII技术管理资源及其泛型实现
前言
RAII的含義是“資源獲取即初始化”。
一段看似安全的代碼
首先看一段代碼:
這段代碼中,我們先進(jìn)行了動(dòng)態(tài)內(nèi)存分配,使用完釋放,看起來(lái)很完美,但是這段程序是否真的保證不會(huì)發(fā)生內(nèi)存泄漏?
考慮這樣一種情形,程序在使用這段內(nèi)存的過(guò)程中throw一個(gè)異常,于是程序轉(zhuǎn)向catch塊,然后XXX。 這段內(nèi)存被釋放了嗎? 顯然沒(méi)有。 那么這段程序應(yīng)該從哪里改進(jìn)呢?
對(duì)象的生命期
考慮一個(gè)問(wèn)題:C++中對(duì)象的聲明期是怎樣的?在C++中,對(duì)象創(chuàng)建的方式有兩種,一種是棧上,一種是堆上創(chuàng)建。
上面的代碼中,第一個(gè)對(duì)象創(chuàng)建在棧上,更明確的說(shuō)法是它是一個(gè)局部變量,這意味著它的生命期起源于被創(chuàng)建的這行語(yǔ)句,終結(jié)與所在作用域的末尾,也就是這里的右花括號(hào)(})。
而第二個(gè)對(duì)象呢?它是采用所謂的動(dòng)態(tài)內(nèi)存分配生成的,需要程序員手工去釋放,當(dāng)調(diào)用delete的時(shí)候才銷(xiāo)毀,但調(diào)用delete的時(shí)機(jī)不是固定的。
也就是說(shuō),棧對(duì)象的生命期是明確的,而堆對(duì)象的生命期由于取決于調(diào)用delete的時(shí)機(jī),因而是不明確的。
看到這里,之前那段有可能內(nèi)存泄漏的代碼如何去改進(jìn)呢?
答案就是用棧對(duì)象明確的生命期去管理資源。
用對(duì)象的生命期管理資源
試想一下,如果我們把之前程序中,對(duì)內(nèi)存的分配寫(xiě)在構(gòu)造函數(shù)中,把釋放資源寫(xiě)在析構(gòu)函數(shù)中,而棧對(duì)象的生命期是明確的,當(dāng)該管理資源的對(duì)象過(guò)期時(shí),連同它管理的資源一起釋放,豈不是非常智能化?
我們嘗試著寫(xiě)出下列代碼:
我們把之前的代碼做如下的改進(jìn):
再來(lái)分析一下這段代碼:
如果正常執(zhí)行,那么當(dāng)執(zhí)行完try塊時(shí),scope對(duì)象過(guò)期,執(zhí)行析構(gòu)函數(shù),同時(shí)釋放了那段數(shù)組。如果使用的過(guò)程中發(fā)生了異常,那么當(dāng)程序進(jìn)入catch塊時(shí),同樣會(huì)銷(xiāo)毀try內(nèi)的局部變量。
無(wú)論是哪種情況,內(nèi)存總是會(huì)被釋放。
如果這里不是int,而是其他復(fù)雜的類(lèi)型,使用這個(gè)封裝的ScopePtr是不是不太方便?顯然不會(huì),我們?nèi)ブ剌d成員操作符就可以了,使它表現(xiàn)的像個(gè)指針,這就是一個(gè)最簡(jiǎn)單的智能指針的產(chǎn)生。
問(wèn)題得到了完美的解決!
資源獲取即初始化
我們上面解決問(wèn)題的辦法就是RAII技術(shù),RAII的含義是“資源獲取即初始化”,這個(gè)概念有兩個(gè)要點(diǎn):
- 獲得資源后立即放進(jìn)管理對(duì)象
- 管理對(duì)象運(yùn)用析構(gòu)函數(shù)確保資源被釋放
看另外一個(gè)例子:我們?cè)谠L問(wèn)一些臨界區(qū)資源的時(shí)候通常需要加鎖,所以產(chǎn)生了下面的代碼:
這種方式是很容易出現(xiàn)問(wèn)題的,例如程序中間遇見(jiàn)錯(cuò)誤情況需要退出這個(gè)函數(shù),此時(shí)很容易忘記解鎖:
此時(shí)如果再次進(jìn)行Lock操作,就造成了死鎖。
解決這個(gè)問(wèn)題的辦法仍然很簡(jiǎn)單,我們?nèi)?xiě)一個(gè)類(lèi):
這樣剛才那段代碼就可以修改成:
這樣,一旦離開(kāi)這段代碼,程序立刻自動(dòng)解鎖。
不過(guò)為了防止錯(cuò)誤使用這個(gè)類(lèi),例如:
可以定義一個(gè)宏:
這樣我們?cè)阱e(cuò)誤使用的時(shí)候,編譯期間就能發(fā)現(xiàn)錯(cuò)誤。
一種泛型解決方案
劉未鵬在他的《C++11(及現(xiàn)代C++風(fēng)格)和快速迭代式開(kāi)發(fā)》中提出了一種泛型實(shí)現(xiàn),利用了C++11的function和Lambda匿名函數(shù),如下:
使用方式也很簡(jiǎn)單:
其實(shí)就是將該資源釋放的函數(shù)代碼段注冊(cè)到Scope類(lèi),其中原理不再贅述。
與其他語(yǔ)言的對(duì)比
RAII是C++獨(dú)有的編程手段。通過(guò)RAII技術(shù)我們能夠做到資源不需要使用時(shí)立即釋放,這是其他GC語(yǔ)言所不具備的。
以Java為例,Java具有完善的GC(Garbage Collection,垃圾回收)機(jī)制,但是存在如下的缺點(diǎn):
- GC只能回收內(nèi)存,而對(duì)于打開(kāi)的文件、數(shù)據(jù)庫(kù)連接等仍然需要手工關(guān)閉。
- GC因?yàn)檫M(jìn)程優(yōu)先級(jí)等原因,回收效率底下,詳情可以參考孟巖的《垃圾收集機(jī)制(Garbage Collection)批判》
conclusion
RAII技術(shù)是現(xiàn)代C++編程技術(shù)中及其重要的一部分,甚至有人稱(chēng)其為“C++編程中最重要的編程技法”,可見(jiàn)其重要性。通過(guò)RAII,我們完全可以實(shí)現(xiàn)資源的自動(dòng)化管理,寫(xiě)出永不內(nèi)存泄漏的程序。
參考資料
- 《C++ Primer》
- 《Effective C++》
- 《Linux多線程服務(wù)器端編程》
總結(jié)
以上是生活随笔為你收集整理的用RAII技术管理资源及其泛型实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql grant查看用户权限命令
- 下一篇: Python字符串的编码与解码(enco