1.2 c++中的健壯指標和資源管理
我最喜歡的堆資源的定義是:「任何在你的程式中獲得並在此後釋放的東西」,記憶體是乙個相當明顯的例子。它需要new來獲得,用delete來釋放,同時也有許多其它的型別資源檔案控制代碼、重要片段、windows中的gdi資源等等。
對於給定的資源的擁有者,是負責釋放資源的乙個物件或者是一段**。所有權分為兩種級別——自動的和顯示的。如果乙個物件的釋放是由語言本身的機制來保證的,這個物件就是被自動的所有。
1.2.1 第一條規則(raii)
乙個指標,乙個控制代碼,乙個臨界區狀態只有在我們將他們封裝入物件的時候才會擁有所有者。這就是我們的第一規則:在建構函式中分配資源,在析構函式中釋放資源。當你按規則將所有資源封裝的時候,你可以保證你的程式中沒有任何的資源洩漏。這點在當封裝物件在棧中建立或者嵌入在其他的物件中的時候非常的明顯。但是對那些動態申請的物件呢?不要急!任何動態申請的東西都被看作一種資源,並且按照上面提到的方法進行封裝。這一物件封裝物件的鏈不得不在某個地方終止。它最終終止在乙個高階的所有者,自動的或者是靜態的。這些分別是對離開作用域或者程式時釋放資源的保證。
下面是資源封裝的乙個經典例子。在乙個多執行緒的應用程式中,執行緒直接共享物件的問題是通過用這樣乙個物件聯絡臨界區來解決的。每乙個需要訪問共享資源的客戶需要獲得臨界區。例如,這是win32下臨界區的實現方法。
class critsect
~critsect()
private:
void acquire()
void release()
private:
critical_section _critsection;
};class lock
~lock()
private:
critsect& _critsect;
};
這裡聰明的部分是我們確保每乙個進入臨界區的客戶租後都可以離開。「進入」臨界區的狀態是一種資源,並應當被封裝。封裝器通常被稱作乙個鎖(lock)。
注意無論發生什麼,臨界區都會借助於語言的機制保證釋放。還有一件需要記住的事情——每一種資源都需要被分別封裝。這是因為資源分配是乙個非常容易出錯的操作,是要資源是有限提供的。我們會假設乙個失敗的資源分配會導致乙個異常——事實上,這會經常的發生。所以如果你想試圖用乙個石頭打兩隻鳥的話,或者在乙個建構函式中申請兩種形式的資源,你可能就會陷入麻煩。只要想想在一種資源分配成功但另一種資源分配失敗丟擲異常時會發生什麼。因為建構函式還沒有全部完成,析構函式不可能被呼叫,第一種資源就會發生洩漏。這種情況可以非常簡單的避免。
1.2.2 smart points
我們至今還沒有討論最常見型別的資源——用操作符new分配,此後用指標訪問的乙個物件。我們需要為每個物件分別定義乙個封裝類嗎?(事實上,c++標準模板庫已經有了乙個模板類,叫做auto_ptr,其作用就是提供這種封裝。我們一會兒再回到auto_ptr。)讓我們從乙個極其簡單、呆板單安全的東西開始。看下面的smart pointer模板類,它十分堅固,甚至無法實現。
template class smartpointer
t *operator->()
t const *operator->() const
protected:
smartpointer() : _p(0) {}
explicit smartpointer(t* p) : _p(p) {}
t* _p;
};
為什麼我們把smartpointer的建構函式設計為protected呢?如果我需要遵守第一條規則,那麼我就必須這樣做。資源——在這裡是class t的乙個物件,必須在封裝器的建構函式中分配。但是我們不能只簡單的呼叫new t,因為我不知道t的建構函式的引數。因為在原則上每乙個t都有乙個不同的建構函式;我需要為他定義另外乙個封裝器。模板的用處會很大,為每乙個新的類,我們可以通過繼承smartpointer定義乙個新的封裝器,並且提供乙個特定的建構函式。
class smartitem : public smartpointer
};
為每乙個類提供乙個smart pointer真的值嗎?說實話——不!他很有教學的價值,但是一旦你學會如何遵循第一規則的話,你就可以放鬆規則並使用一些高階的技術。這以技術是讓smartpointer的建構函式成為public,但是只是用它來做資源轉換(resource transfer)我的意思是用new操作符的結果直接作為smartpointer的建構函式的引數,像這樣:
smartpointeritem (new item(i));
這個方法明顯需要自控性,不只是你,而且包括你的程式小組的每乙個成員。他們都必須發誓除了作資源轉換外不把建構函式用在其他用途。幸運的是這條規則很容易得到加強。只需要在原始檔中查詢所有的new即可。
1.2.3 resource transfer
到目前為止,我們所討論的一直是生命週期在乙個單獨的作用域內的資源。現在我們要解決乙個困難的問題——如何在不同的作用域間安全的傳遞資源。這一問題當你在處理容器問題的時候會變的十分明顯。你可以動態的建立一串物件,將它存放至乙個容器中,然後將它取出,並且在最終安排他們。為了能夠讓這安全的工作——沒有洩露——物件需要改變其所有者。
這個問題乙個非常顯而易見的解決方法是使用smart pointer,無論是在加入容器前還是找到他們後。
template t* smartpointer::release()
注意在release呼叫後,smartpointer就不再是物件的所有者——它內部的指標指向空。現在,呼叫release都必須是乙個負責的人並且迅速隱藏返回的指標到新的所有者物件中。在我們的例子中,容器呼叫了release,比如這個stack的例子:
void stack::push(smartpointer & item) throw(char*)
同樣的,你也可以在你的**中加強release的可靠性。 c 徹底消滅 記憶體洩漏 野指標 下篇
6.後記 前篇最後,我們為消除記憶體洩漏 野指標等問題所做的 嘗試還是存在問題,本篇我們來討論一下進一步的改進。使用者只new開闢,但是忘記使用delete釋放,程式需要定期自動清理記憶體。在之前的記憶體管理中,部分指標需要用delete,部分指標不能用delete,這導致使用者的困惑。後續改進中,...
C 記憶體管理 C 記憶體分類
c 記憶體管理 記憶體分類 moakap 在編寫程式過程中,程式設計師必須清楚程式記憶體的分配機制,合理進行記憶體管理,這樣才能得到高效的程式。同時,如果對c 記憶體分配基本概念不理解,使用不當,一方面浪費了寶貴的記憶體資源,降低了程式執行效率,另一方面還會造成程式中意想不到的錯誤。在 c 程式中,...
C 記憶體管理
在嵌入式系統中使用c 的乙個常見問題是記憶體分配,即對new 和 delete 操作符的失控。具有諷刺意味的是,問題的根源卻是c 對記憶體的管理非常的容易而且安全。具體地說,當乙個物件被消除時,它的析構函式能夠安全的釋放所分配的記憶體。這當然是個好事情,但是這種使用的簡單性使得程式設計師們過度使用n...