天天看點

RAII和unique_ptr

raii

raii是resource acquisition is

initialization的縮寫,是在面向對象(object-oriented)語言中使用的一種程式設計習慣,主要是用來在c++中處理異常安全資源管理(exception-safe

resource management)。

在raii中,資源的擷取和釋放和對象的聲明周期緊密聯系在一起,當對象構造的時候,在構造函數中申請資源( resource

allocation),而在對象的析構函數中釋放資源(resource

deallocation),當析構函數正确執行時,就不會有資源洩露,這讓我想起了linux核心驅動,在編寫驅動的過程中,一般也是在init函數中申請資源,然後在exit函數中釋放資源,能夠避免資源洩露,是以這種思想普遍運用在程式設計中。

raii作為一種資源管理的技術主要有以下優點:

封裝性,因為将異常處理的邏輯寫到了構造和析構函數中,是以不用調用者關心怎麼對資源進行管理。

異常安裝,對于棧對象,當異常發生的時候,異常處理會在離開目前scope的時候,執行對象的析構函數,進而釋放記憶體。

可定位,能夠将資源申請和釋放的邏輯集中寫在構造和析構中,不會散亂在各個地方。

raii作為一種資源管理技術,主要運用于下面幾個地方: 1.

在多線程環境下控制同步鎖。在多線程中,為了線程間同步,經常要申請、釋放鎖,而有時候,經常會出現申請了,但是沒有或者由于異常等原因沒有釋放鎖,進而出現死鎖。對于這種申請、釋放鎖的共走,可以交給對象的構造和析構函數,這樣可以将申請、釋放封裝起來,并且保證了異常安全。

2. 檔案處理,一般在處理檔案的時候,我們都要先open-write/read-close,對于這種固定的結構,完成可以将read-write封裝起來。 3.

動态對象的所有權問題。針對動态對象的所有權,c++提供了<code>smart pointer</code>,其中<code>std::unique_ptr</code>針對單擁有權問題,而<code>std::shared_ptr</code>則是針對共享對象。

例子:

gnu針對c提供了一種非标準的擴充來支援raii:。下面是一個例子:

下面對前面提到的unique_ptr進行介紹。

unique_ptr是c++11新增的一個特性,作為一種新的smart pointer,主要用于管理隻有單一擁有權的動态對象。

基本用法:

unique_ptr有着智能指針的優點,在析構函數中會自動釋放資源。

在c++11之前有auto_ptr用來做動态對象的管理,但是複制auto_ptr會将控制權從右值轉移到左值,原先的右值将不再擁有對象的控制權,這和傳統的“複制”語義不符,而且由于這種複制語義,導緻了auto_ptr不能被用在标準的容器中。在c++11中出現了unique_ptr開解決這個問題。使用unique_ptr的時候,unique_ptr可以存儲在容器中,當容器析構的時候unique_ptr指向的對象也被釋放。

那c++11是怎麼解決不能存在在容器中的問題的呢?通過添加了rvalue reference和move語義。

rvalue reference是c++的一個小擴充,rvalue

reference允許coder避免邏輯上不必要的複制,并且提供了一個完美的轉發功能。主要目的是幫助設計高性能、健壯的庫。在中有對rvalue reference的介紹。那為什麼要引入rvalue

reference,引入rvalue reference解決了什麼問題呢?

傳遞給函數參數的時候,我們可以傳值或者傳引用。在c中,傳引用是通過傳遞指針,而指針實際上就是傳遞的一個位址,在c/c++中,記憶體模型就是申請一個box,然後在這個box中存放資料,而傳遞的時候就是傳遞這個box的位址,對于這個box怎麼管理進而引申出好多問題。但是不是所有的資料都有位址,譬如某些存放在寄存器中的資料,此時就沒有位址了,是以,對于有位址的資料我們叫做<code>lvalues</code>,而沒位址的資料叫<code>rvalues</code>。那實際中有哪些<code>rvalues</code>,譬如函數的傳回值,數值計算式等。

下面是一個例子:

上面這個是無法通過的,因為傳入的參數是右值,沒有位址,一個簡單的修改如下:

那為什麼編譯器不幫我們建立臨時變量,然後不再報錯呢?

讓我們想下,一般當參數是位址的時候,我們在函數内部是希望改變指向的對象的值,如:

此時我們如果傳入一個臨時變量,對于這個函數内部的操作就變的沒有意義了。是以,此時編譯器就會報錯,不允許将rvalue的位址傳入。

在c++中,除了指針外,還加入了引用,于是,上面的代碼可以重寫如下:

此時還是無法得到rvalue的引用(reference),但是有時候傳遞引用并不是為了改變其值,對于一些大的資料,傳遞引用可以減少開銷,此時并不對lvalue和rvalue有要求。是以,編譯器因該能夠允許傳遞rvalue的reference。

當我們不會改變傳遞進來的reference的值時,則傳遞進來lvalue或者rvalue都是可以的,是以,上面的代碼如果改成下面的:

此時看起來一切都挺好的,但是引入ato_ptr後,就會帶來一系列的問題。

現在總結下reference和lvalue和rvalue的關系:

reference可以綁定到lvalues

const reference可以綁定到lvalues和rvalues,但是不允許改變源值

上面看上去都挺好的,除了不能将reference綁定到rvalues,并且修改,但是誰又想要改變一個臨時的值呢?

auto_ptr是智能指針中的一員,用來自動釋放指向的記憶體塊,auto_ptr有value的語義,可以傳遞,在棧上建立,或者是其他資料的長成員,在傳遞給函數的時候是通過值傳遞,但是當auto_ptr進行值傳遞的時候,其指向的資料并不複制,這又像reference的行為,同時可以對auto_ptr使用*和-&gt;,就像指針一樣。

考慮下面的代碼:

考慮上面的代碼,在create()内部,當結束的時候,剛離開scope的時候,如果不做什麼,編譯器會調用auto_ptr的析構函數,進而釋放記憶體;函數create傳回的是一個rvalue。

對于第一點,調用析構函數,我們希望将其賦給ap,此時因該調用拷貝構造函數,而對于右值有下面的兩個問題:

如果我們定義拷貝構造函數的source為const reference,則此時無法修改source,将擁有權轉移。

如果定義非const的reference,此時不能夠綁定到右值。

對于上面的問題,我們需要的是rvalue

reference,能夠綁定到rvalue,并且修改他。于是針對auto_ptr不能複制構造右值,出現了<code>unique_ptr</code>,<code>unique_ptr</code>有下面的複制構造函數:

rvalue reference能夠同時綁定到lvalue和rvalue,并且不阻止改變值。

auto_ptr還有一點問題是,不能夠存儲在大多數容器中,為什麼呢?考慮下面的一個例子:

而在容器中,會有臨時變量,一旦拷貝,則容器中的值将不再有所有權,這是嚴重的錯誤。

但是有時候我們又想要将控制權進行轉移,此時我們應該明确的告知說我們現在要轉移控制權了,是以不要再去使用原先的源了。

此處我們總結下rvalue和lvalue的不同,rvalue會在指派後消失,而lvalue則保持不變。我們看到上面的unique_ptr的複制構造函數:

這會同時綁定rvalue好lvalue,是以我們需要下面的一個重載函數:

重載rvalue,此時我們要做的就是将rvalue的構造私有化,現在unique_ptr隻保持轉移控制權的語義了,而當我們想要将一個lvalue轉移到unique_ptr的時候,此時使用move函數,

明确的将lvalue變為rvalue,move作用就是将一個lvalue轉成rvalue,

下面我們接着介紹unique_ptr,unique_ptr中unique在此處是指什麼呢?此處unique表示,當你建立一個對象的時候,隻會有一份拷貝,隻會有一個指針。

平時我們使用指針的時候,經常會碰到下面的情形:

首先建立一個對象,然後将指針傳遞給make_use,此時在make_use會對指針做什麼呢?make_use會拷貝指針,稍後使用嘛?或者make_use會釋放指針嗎?我們無法很好的回答這些問題,因為c++不能保證make_use對指針的使用規範,我們隻能通過檢查代碼來保證不會錯誤的時候指針。這些問題可以通過<code>unique_ptr</code>解決,隻保證一份拷貝,不會有其他拷貝。

現在我們使用指針的時候,将其放入到unique_ptr,保證擁有權是唯一的,不會隐式轉移,要轉移時通過明确的move函數将其轉成rvalue,進行轉移,此處,使用unique_ptr後,會帶來的一點不同是,我們将會傳遞引用,将其當做值來傳遞,如下面:

本文首先介紹了raii,即resource acquisition is

initialization一種資源安全管理的方式,然後引出了smart

point,随後又介紹了auto_ptr,早期對raii的一種嘗試,後來由于其存在的一些問題,通過介紹lvalue、rvalue的概念,最後在c++11中給出了解決方案,unique_ptr,并給出move語義,明确的将左值轉換為rvalue,進行控制權的轉移。

參考:

http://en.wikipedia.org/wiki/resource_acquisition_is_initialization

http://www.drdobbs.com/cpp/c11-uniqueptr/240002708

http://bartoszmilewski.com/2008/10/18/who-ordered-rvalue-references-part-1/

上一篇: 集合
下一篇: Swift