史萊姆論壇

返回   史萊姆論壇 > 教學文件資料庫 > 程式 & 網頁設計技術文件
忘記密碼?
論壇說明

歡迎您來到『史萊姆論壇』 ^___^

您目前正以訪客的身份瀏覽本論壇,訪客所擁有的權限將受到限制,您可以瀏覽本論壇大部份的版區與文章,但您將無法參與任何討論或是使用私人訊息與其他會員交流。若您希望擁有完整的使用權限,請註冊成為我們的一份子,註冊的程序十分簡單、快速,而且最重要的是--註冊是完全免費的!

請點擊這裡:『註冊成為我們的一份子!』

Google 提供的廣告


 
 
主題工具 顯示模式
舊 2006-05-30, 10:51 AM   #1 (permalink)
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設 軟體 - C++:最強大的.NET語言之記憶體與資源

C++:最強大的.NET語言之記憶體與資源

當執行環境中包含LJ回收機制時,區別開記憶體管理和資源管理,就非常重要了。典型地來說,LJ回收器只對包含對象的記憶體之分配與釋放感興趣,它可不關心你的對象是否擁有其他的資源,如資料庫連接或核心對象的句柄。

  記憶體管理

  本機C++為程式員提供了超越記憶體管理的直接控制能力,在堆疊上分配一個對象,意味著只有在進入特定函數時,才會為對像分配記憶體,而當函數返回或堆疊展開時,記憶體被釋放。可使用操作符new來動態地為對像分配記憶體,此時記憶體分配在CRT堆中,並且需要程式員顯存地對對像指針使用操作符delete,才能釋放它。這種對記憶體的精確控制,也是C++可用於編寫極度高效的程式的原因之一,但如果程式員不小心,這也是記憶體洩漏的原因。另一方面,你不需要求助於LJ回收器來避免記憶體洩漏--實際上這是CLR所採取的方法,而且是一個非常有效的方法,當然,對於LJ回收堆,也有其他一些好處,如改進的分配效率及引用位置相關的優勢。所有這一切,都可以在C++中通過庫支持來實現,但除此之處,CLR還提供了一個單一的記憶體管理編程模型,其對所有的編程語言都是通用的,想一想與C++中COM自動化對像相交互和調度資料類型所需做的一切工作,就會發現其重要意義所在--橫跨數種編程語言的LJ回收器,作用是非常巨大的。

  為了效率,CLR也保留了堆疊的概念,以便值類型可在其上分配,但CLR也提供了一個newobj中間語言指令,以在托管堆中分配一個對象,但此指令只在C#中對引用對像使用操作符new時提供。在CLR中,沒有與C++中的delete操作符對應的函數,當應用程式不再引用某對像時,分配的記憶體最後將由LJ回收器回收。

  當操作符new應用於引用類型時,托管C++也會產生newobj指令,當然,對此使用delete操作符是不合法的。這確實是一個矛盾,但同時也證明了為什麼用C++指針概念來表示一個引用類型不是一個好的做法。

  在記憶體管理方面,除了上述在對像構造一節討論過的內容,C++/CLI沒有提供任何新的東西;資源管理,才是C++/CLI的拿手好戲。

  資源管理

  CLR只有在資源管理方面,才能勝過本機C++。Bjarne Stroustrup的"資源獲取即初始化"的技術觀點,基本定義了資源類型的模式,即類的構造函數獲取資源,析構函數釋放資源。這些類型是被當作堆疊上的局部對象,或複雜類型中的成員,其析構函數自動釋放先前分配的資源。一如Stroustrup所言"對LJ回收機制來說,C++是最好的語言,主要是因為它產生很少的LJ。"

  也許有一點令人驚訝,CLR並沒有對資源管理提供任何顯式執行時支持,CLR不支持類似析構函數的C++概念,而是在 .NET Framework中,把資源管理這種模式,提升到一個IDisposable核心接頭類型的中心位置。這種想法源自包裝資源的類型,理應實現此接頭的單一Dispose方法,以便調用者在不再使用資源時,可調用該方法。不必說,C++程式員會認為這是時代的倒退,因為他們習慣於編寫那些預設狀態下清理就是正確的代碼。

  因為必須要調用一個方法來釋放資源,由此帶來的問題是,現在更難編寫"全無異常"的代碼了。因為異常隨時都可能發生,你不可能只是簡單地在一段代碼後,放置一個對對象的Dispose方法的調用,這樣做的話,就必須要冒資源洩漏的風險。在C#中解決這個問題的辦法是,使用try-finally塊和using語句,在面對異常時,可提供一個更可靠的辦法來調用Dispose方法。有時,構造函數也會使用這種方法,但一般的情況是,你必須要記住手工編寫它們,如果忘記了,產生的代碼可能會存在一個悄無聲息的錯誤。對缺乏真正析構函數的語言來說,是否需要try-finally塊和using語句,還有待論證。

using (SqlConnection connection = new SqlConnection("Database=master; Integrated Security=sspi"))
{
 SqlCommand command = connection.CreateCommand();
 command.CommandText = "sp_databases";
 command.CommandType = CommandType.StoredProcedure;

 connection.Open();

 using (SqlDataReader reader = command.ExecuteReader())
 {
  while (reader.Read())
  {
   Console.WriteLine(reader.GetString(0));
  }
 }
}

  對托管C++來說,情節也非常類似,也需要使用一個try-finally語句,但其是Microsoft對C++的擴展。雖然很容易編寫一個簡單的Using模板類來包裝GCHandle,並在模板類的析構函數中調用托管對象的Dispose方法,但托管C++中依然沒有C# using語句的對等物。

Using<SqlConnection> connection(new SqlConnection(S"Database=master; Integrated Security=sspi"));

SqlCommand* command = connection->CreateCommand();
command->set_CommandText(S"sp_databases");
command->set_CommandType(CommandType::StoredProcedure);

connection->Open();

Using<SqlDataReader> reader(command->ExecuteReader());

while (reader->Read())
{
 Console::WriteLine(reader->GetString(0));
}

  想一下C++中對資源管理的傳統支持,其對C++/CLI也是適用的,但C++/CLI的語言設計猶如為C++資源管理帶來了一陣輕風。首先,在編寫一個管理資源的類時,對大部分CLR平台語言來說,其中一個問題是怎樣正確地實現Dispose模式,它可不像本機C++中經典的析構函數那樣容易實現。當編寫Dispose方法時,需要確定調用的是基類的Dispose方法--若有的話,另外,如果選擇通過調用Dispose方法來實現類的Finalize方法,還必須關注並發訪問,因為Finalize方法很可能被不同的線程所調用。此外,與正常程式代碼相反,如果Dispose方法實際上是被Finalize方法調用的,還需要小心仔細地釋放托管資源。

  C++/CLI並沒有與上述情況脫離得太遠,但它提供了許多幫助,在我們來看它提供了什麼之前,先來快速回顧一下如今的C#和托管C++有多麼接近。下例假設Base從IDisposable派生。

class Derived : Base
{
 public override void Dispose()
 {
  try
  {
   //釋放托管與非托管資源
  }
  finally
  {
   base.Dispose();
  }
 }

 ~Derived() //實現或重載Object.Finalize方法
 {
  //只釋放非托管資源
托管C++也與此類似,看起來像析構函數的代碼其實是一個Finalize方法,編譯器實際上插入了一個try-finally塊並調用基類的Finalize方法,因此,C#與托管C++相對容易編寫一個Finalize方法,但在編寫Dispose方法時,卻沒有提供任何幫助。程式員們經常使用Dispose方法,把它當作一個偽析構函數以便在代碼塊末執行一點其他的代碼,而不是為了釋放任何資源。

  C++/CLI認識到了Dispose方法的重要性,並在引用類型中,使之成為一個邏輯"析構函數"。

ref class Derived : Base
{
 ~Derived() //實現或重載IDisposable:ispose方法
 {
  //釋放托管與非托管資源
 }

 !Derived() //實現或重載IDisposable:ispose方法
 {
  //只釋放非托管資源
 }
};

  對C++程式員來說,這讓人感覺更自然了,能像以往那樣,在析構函數中釋放資源了。編譯器會產生必要的IL(中間語言)來正確實現IDisposable:ispose方法,包括抑制LJ回收器調用對象的任何Finalize方法。事實上,在C++/CLI中,顯式地實現Dispose方法是不合法的,而從IDisposable繼承只會導致一個編譯錯誤。當然,一旦類型通過編譯,所有使用該類型的CLI語言,將只會看到Dispose模式以其每種語言最自然的方式得以實現。在C#中,可以直接調用Dispose方法,或使用一個using語句--如果類型定義在C#中。那麼C++呢?難道要對堆中的對象正常地調用析構函數?此處當然是使用delete操作符了,對一個句柄使用delete操作符將會調用此對象的Dispose方法,而回收對象的記憶體是LJ回收器該做的事,我們不需要關心釋放那部分記憶體,只要釋放對象的資源就行了。

Derived^ d = gcnew Derived();
d->SomeMethod()
delete d;

  如果表達式中傳遞給delete操作符的是一個句柄,將會調用對象的Dispose方法,如果此時再沒有其他對像鏈接到引用類型,LJ回收器就會釋放對像所佔用的記憶體。如果表達式中是一個本機C++對象,在釋放記憶體之前,還會調用對象的析構函數。

  毫無疑問,在對像生命期管理上,我們越來越接近自然的C++語法,但要時刻記住使用delete操作符,卻不是件易事。C++/CLI允許對引用類型使用堆疊語義,這意味著你能用在堆疊上分配對象的語法來使用一個引用類型,編譯器會提供給你所期望的C++語義,而在底層,實際上仍是在托管堆中分配對象,以滿足CLR的需要。

Derived d;
d.SomeMethod();

  當d超出範圍時,它的Dispose將會被調用,以釋放它所佔用的資源。再則,因為對像實際是在托管堆中分配的,所以LJ回收器會在它的生命期結束時釋放它。來看一個ADO.NET的例子,它與C++/CLI中的概念非常相似。

SqlConnection connection("Database=master; Integrated Security=sspi");

SqlCommand^ command = connection.CreateCommand();
command->CommandText = "sp_databases";
command->CommandType = CommandType::StoredProcedure;

connection.Open();

SqlDataReader reader(command->ExecuteReader());

while (reader.Read())
{
 Console::WriteLine(reader.GetString(0));
}
 
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次
 



發表規則
不可以發文
不可以回覆主題
不可以上傳附加檔案
不可以編輯您的文章

論壇啟用 BB 語法
論壇啟用 表情符號
論壇啟用 [IMG] 語法
論壇禁用 HTML 語法
Trackbacks are 禁用
Pingbacks are 禁用
Refbacks are 禁用


所有時間均為台北時間。現在的時間是 03:51 AM


Powered by vBulletin® 版本 3.6.8
版權所有 ©2000 - 2024, Jelsoft Enterprises Ltd.


SEO by vBSEO 3.6.1