C# vs C++之二:GC vs RAII

來源:互聯網
上載者:User

資源管理

在C語言中,資源管理是一個極為繁瑣易錯的工作,大多複雜的C系統都面臨著記憶體泄露、懸掛指標等問題。這是一方面是由底層語言的特點決定;另一方面也是由於C語言特性相對較少,嚴重依賴程式員進行正確的資源管理,缺乏有效支援手段。

C#和C++兩門語言的定位不同,它們在資源管理方面採取了兩種截然不同的方式:一為GC,一為RAII。GC讓程式建立在更高的抽象層次上,使資源管理變得更方便,更安全;而RAII則保留了C的底層能力,同時在C++特性的支援下提供了簡單有效資源管理方式。我們知道C++最激烈的批評往往來自於C社區,而在我看來C程式員可以不接受虛函數,不接受模板,但有什麼理由不接受RAII呢?可以說RAII是C++相對C來說幾乎無副作用的明顯進步。

下面就從GC開始:

引用代替指標

C#通過CLR管理託管記憶體,用引用抽象代替指標間接操作託管記憶體,讓程式員在更高的層次上安全地使用資源。這使得C#失去了直接管理記憶體的能力,但換來了以下好處:

1.型別安全:在C/C++中可以通過類型轉換把整數或其他類型的指標轉換為特定類型的指標,這意味著指標是非型別安全的,必須由程式員來保證指標代表的記憶體空間的合法性。而C#引用可以看作是型別安全的指標,as運算子可以保證轉換的型別安全。

2.記憶體整理:建立對象需要從堆中動態分配連續的記憶體空間,由於不同對象的記憶體大小是不同的,常見的最初相符和最優匹配堆分配演算法都會造成堆中的記憶體片段問題。片段的存在使實際可用記憶體小於實體記憶體,所以應盡量減少片段的產生。一個方向是設計更好的記憶體配置演算法;另一個方向是通過周期性地進行記憶體整理調整最佳化。在C/C++中,由於指標代表了絕對位址,因此不存在通用的記憶體整理演算法;而C#屏蔽了指標,通過引用操作對象,就使得記憶體整理成為可能。PS:這並不意味著C/C++記憶體配置就弱於C#,C/C++程式可以為某種類型的對象設計專用的記憶體配置方式,甚至把對象指定分配到某一物理地址空間,這些都是C#不具備的。

託管和非託管資源

在C#中,資源分為託管資源和非託管資源兩種。GC在回收無用對象資源時,可以自動回收託管資源(比如託管記憶體),但對於非託管資源(比如Socket、檔案、資料庫連接)必須在程式中顯式釋放。

託管資源的回收首先需要GC識別無用對象,然後回收其資源。一般無用對象是指通過當前的系統根對象和呼叫堆疊對象不可達的對象。對象有一個重要的特點導致無用對象判斷的複雜性:對象間的循環參考!如果沒有循環參考,就可以通過“引用計數”這種簡單高效的方式實現無用對象的判斷,並實現即時回收。正是由於循環參考的存在導致GC需要設計更為複雜的演算法,這樣帶來的最大問題在於喪失了資源回收的即時性,而變成一種不確定的方式。

對於非託管資源的釋放,C#提供了兩種方式:

1.Finalizer:寫法貌似C++的解構函式,本質上卻相差甚遠。Finalizer是對象被GC回收之前調用的終結器,初衷是在這裡釋放非託管資源,但由於GC運行時機的不確定性,通常會導致非託管資源釋放不及時。另外,Finalizer可能還會有意想不到的副作用,比如:被回收的對象已經沒有被其他可用對象所引用,但Finalizer內部卻把它重新變成可用,這就破壞了GC垃圾收集過程的原子性,增大了GC開銷。

2.Dispose Pattern:C#提供using關鍵字支援Dispose Pattern進行資源釋放。這樣能通過確定的方式釋放非託管資源,而且using結構提供了異常安全性。所以,一般建議採用Dispose Pattern,並在Finalizer中輔以檢查,如果忘記顯式Dispose對象則在Finalizer中釋放資源。

可以說,GC為程式帶來安全方便的同時也付出了不小的代價:一則喪失了託管資源回收的即時性,這在即時系統和資源受限系統中是致命的;二則沒有把託管資源和非託管資源的管理統一起來,造成概念割裂。C++的定位之一是底層開發能力,所以不難理解GC並沒有成為C++的語言特性。雖然我們在C++0x和各種第三方庫都能看到GC的身影,但GC對於C++來講並不是那麼重要,至多是一個有益的補充。C++足以傲視C,並和C# GC一較高下的是它的RAII。

棧語義

在介紹RAII之前,讓我們先來看一道C++面試題:“重構下面的代碼,在保證正確釋放資源的情況下,去掉多餘的try catch”

//C++

void f(){

    try{

        int *ptr = new int(123);

        …//do something with ptr

        delete ptr;

    }

    catch {

        delete ptr;

    }

}

代碼中new int在堆上分配記憶體,並通過delete小心翼翼地釋放記憶體。這是典型的C風格的C++代碼,雖然用了try、catch等進階文法,但資源管理方式依舊是C。按C++特有的方式可以重構成這樣:

//C++

//定義資源代理類模板

template<typename T>

class Resource{

public:

    Resource(T *ptr) { this->ptr = ptr;} //建構函式中初始化資源

    ~Resource() { delete ptr; } //解構函式中釋放資源

    T& operator*() { return *ptr; } //重載*運算子

    T* operator->() { return ptr; } //重載->運算子

    //…省略了拷貝建構函式和賦值運算子等

private:

   T* ptr;

};

void f(){

    Resource<int> r(new int(123));

    //do something with r

}

f函數中,我們在棧上建立了一個資源模板類Resource的對象r,並通過r來提供服務。只是這麼簡單的一封裝,就省掉了繁瑣易錯的try,catch,不管f內部出什麼問題,拋什麼異常,都能保證r所管理的記憶體資源最終被正確釋放。C++保證一旦離開詞法範圍,在任何情況下都會調用棧上對象的解構函式,這就是所謂的“棧語義”(stack semantics)。事實上,STL已經有auto_ptr這個智能指標類模板,其實現和上面的Resource類模板類似。

RAII

RAII是resource acquisition is initialization的縮寫,意為“資源擷取即初始化”。它是C++之父Bjarne Stroustrup提出的設計理念,其核心是把資源和對象的生命週期綁定,對象建立擷取資源,對象銷毀釋放資源。在RAII的指導下,C++把底層的資源管理問題提升到了對象生命週期管理的更高層次。上面的例子,我們把new所擷取的記憶體塊視為資源,把r對象視為資源的代理對象,r應負責資源的擷取和釋放。在棧語義和操作符重載的支援下,C++的RAII體現出了簡潔、安全、即時的特點:

1.概念簡潔性:讓資源(包括記憶體和非記憶體資源)和對象的生命週期綁定,資源類的設計者只需用在類定義內部處理資源問題,提高了程式的可維護性

2.型別安全:通過資源代理對象封裝資源(指標變數),並利用運算子多載提供指標運算方便使用,但對外暴露型別安全的介面

3.異常安全性:棧語義保證對象解構函式的調用,提高了程式的健壯性

4.釋放即時性:和GC相比,RAII達到了和手動釋放資源一樣的即時性,因此可以承擔底層開發的重任

也許你還在驚訝RAII如此簡單的時候,關於RAII的主要內容已經介紹完了。簡單不意味著簡陋,在我看來RAII雖然不像GC一樣,是一套具體的機制,但它蘊含的對象與資源關係的哲學深度的理解卻使得我對Bjarne Stroustrup肅然起敬!

最後,不得不提醒RAII的理念固然簡單,不過在具體實現的時候仍有需要小心的地方。比如對於STL的auto_ptr,可以視為資源的代理對象,auto_ptr對象間的賦值是一個需要特別注意的地方。簡單說來資源代理對象間賦值的語義不滿足“賦值相等”,其語義是資源管理權的轉移。

什麼是“賦值相等”呢?比如:

int a;

int b = 10;

a = b; //這句話執行後 a == b

但對於資源代理對象,這是不滿足的,比如:

auto_ptr<int> a(null);

auto_ptr<int> b(new int(123));

a = b; //這句話執行後a != b,賦值的語義是b把資源的管理權交給了a

總結

對比介紹了C#和C++的資源管理方式:GC和RAII。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.