從C++到C++/CLI


本文轉載於:https://www.cnblogs.com/feisky/archive/2009/11/22/1607999.html

劉未鵬(pongba) /文
  

  看起來只是在C++后面多寫了一個“/CLI”,然而其意義卻遠不止於此,google的c++.moderated版上為此還發起了數星期的討論,在國內大部分人對C++/CLI還不是很了解的情況下,google上面已然硝煙四起...
  就像我們作出其它任何選擇一樣,在選擇之前最重要的是先要清楚為什么作出這樣或那樣的選擇——C++/CLI到底提供了哪些優勢?為什么我們(標准C++程序員)要選擇C++/CLI而不是C#?我們能夠得到什么?CLI平台會不會束縛C++的能力?
  這些都是來自標准C++社區的疑問。從google上面的討論看來,更多來自標准C++社區的程序員擔心的是C++/CLI會不會約束標准C++的能力,或者改變標准C++發展的方向,也有一部分人對C++/CLI的能力持懷疑態度。另外一些人則是詢問C++/CLI能夠帶來什么。
  這些被提出的問題在google上面一一得到了答案。好消息是:情況比樂觀的人所想象的或許還要更好一些——
  世界改變了嗎?
  對於諳於標准C++的程序員來說,最為關心的還是:在C++/CLI中,世界還是他們熟悉的那個世界嗎?在標准C++的世界里,他們手里的各種魔棒——操作符重載|模板|多繼承(語言),STL|Boost|ACE(庫)——還能揮舞出五彩繽紛的火焰嗎?是不是標准C++到了.NET環境下就像被拔掉了牙的老虎一樣——Managed C++ Extension的陰影是不是還籠罩在他們的心頭?
  答案是:以前你所能做的,現在仍然能做,世界只是變得更廣闊了——
  什么是C++/CLI?
  l C++/CLI是一集標准化的語言擴展(對標准C++進行擴展),而並非另起爐灶的另一門新語言。所以C++/CLI是標准C++的一個超集。
  l C++/CLI是一門ECMA標准[1](並且會被提交給ISO標准化委員會),而不是微軟的專有語言。參與C++/CLI標准的修訂的有很多組織(或公司),其中包括Edison Design Group,Dinkumware公司等,微軟在把C++/CLI標准草案提交給ECMA組織后就放棄了對其的控制權,而是將它作為一份公開發展的標准,任何使用C++/CLI的用戶都可以為它的發展提出自己的建議。
  l C++/CLI的目的是把C++帶到CLI平台上,使C++能夠在CLI平台上發揮最大的能力。而並非把C++約束在CLI平台(CLI本身也是ISO標准化的)上。相反,原來標准C++的能力絲毫沒有減弱,並且,通過C++/CLI中的標准擴展,C++具有了原來沒有的動態編程能力以及一系列的first class的.NET特性。這些擴展並非是專有的,而是以一種標准的方式呈現。
  C++/CLI有什么優越性?
  l 動態編程和refelection——標准C++是一門非常靜態的語言,其原則是盡量在編譯期對程序的合法性和邏輯作出檢查。而在運行時的動態信息方面,標准C++是有所欠缺的。例如,標准C++在運行期能夠對動態對象進行查詢的就只有typeid操作符,而typeid()返回的typeinfo類雖然是個唯一標識,但是也僅僅止於“唯一”而已,首先標准C++並未規定typeinfo的底層二進制表示,所以用它作為跨平台的類唯一標識符就不可能了,其次typeinfo類幾乎僅僅就表示類名字而已,這種非常“薄”的運行時類型信息阻止了標准C++在分布式領域的能力(IDL就是為了彌補標准C++在運行期類型信息的不足,但是IDL對於擁有元數據的語言如JAVA或C#根本就不是必須的,同時IDL也使C++在分布式領域的使用不那么簡易)。由於標准C++的Native特點,所以其代碼一經編譯便幾乎喪失所有的類型信息,從而使得運行期無法對程序本身做幾乎任何的改動,換句話說,標准C++的代碼一經編譯就幾乎變成了死的。而C++/CLI改變了這一現狀,C++/CLI擁有完備的元數據,允許程序在運行期查詢完整的類型信息,並可以由類型信息動態創建對象,甚至可以動態創建類型,添加方法等等,這種強大的運行期的動態特性對於現代應用領域(例如分布式WEB應用)是必須的。
  l GC——現在誰也不會說“往C++中加入GC就是終結了C++”這種話了。就連Bjarne Stroustrup也同意如果C++要被用於大型或超大型的軟件開發中去,最好要有一個良好的可選的GC支持。GC與否,已經不再是個值得爭論的問題,問題是,我們如何把它實現的更好——C++/CLI中的GC是目前最為強大的GC機制之一——分代垃圾收集。GC的好處是可以簡化軟件的開發模型,在效率並非極其關鍵的領域,GC可以很大程度上提高生產率。C++/CLI中的GC很重要的一點是:它正是可選的。這一點不同於JAVA或C#,對於后者,GC無處不在,對象只能分配在托管堆上。而在C++/CLI中,如果把你的對象分配在Native Heap上,你就得到和標准C++一樣的高效內存管理。如果把對象分配在Managed Heap上,那么該對象的內存就由GC來自動回收。這種混合式的內存管理環境和C++/CLI的定位有關——畢竟,C++/CLI的定位是.NET平台上的系統級編程語言,所以效率以及對底層的控制很重要,故保留了Native Heap。后面你會看到這種編程環境的優點。
  l BCL——.NET平台上的基礎類庫,BCL中豐富的類極大的方便了開發者。C++/CLI可以完全使用BCL中的任何類。
  l 可移植性——毫無疑問,可移植性是個至關重要的問題,特別是對於標准C++社群的人們。C++/CLI對這個問題的答案是:“如果你的代碼不依賴於本地二進制庫,就可以“一次編譯,隨處運行(在.NET平台上)”(使用“/clr:pure”編譯選項將代碼編譯成可移植的純MSIL代碼)。如果你的代碼某部分依賴於本地的二進制庫(如C輸入輸出流庫),那么這些部分仍然是源代碼可移植的,而其它部分則可以“一次編譯,隨處運行”。對於標准C++來說,向來保證的只是源代碼的可移植性,所以我們並沒有失去什么,相反,如果遵守協定——不用本地二進制庫,例如,用BCL里的輸入輸出流庫代替C輸入輸出流庫——你就可以得到“一次編譯,隨處運行”的承諾,也就是說,你的代碼經過編譯(/clr:pure)后可以在其它任何.NET平台上運行——Unix,Linux下的Mono(移植到Unix,Linux下的.NET),以及FreeBSD,Mac OSX下的Rotor(.NET的開放源代碼項目),等等。
  習慣了標准C++輸入輸出流的程序員可能要抱怨了——我們為什么要使用BCL里面的輸出輸出流?標准的iostream已經很好了!這里其實有一個解決方案,使用 iostream的代碼之所以不能“一次編譯,隨處運行”是因為代碼要依賴於本地的二進制lib文件,而如果可以把iostream的實現重新也編譯成純MSIL代碼,那么使用它的代碼編譯后就完全可隨處運行了。目前,這是個有待商榷的方案。不過,至少當面對“總得依賴於某些平台相關的二進制代碼”這種情況時,可以把平台相關的代碼封裝成DLL文件——對各個目標平台編譯成不同的二進制版本,而程序的其它部分仍然只需一次編譯即可,只要使用.NET的P/Invoke就可以對不同平台調用相應的DLL了。
  l 效率——作為.NET平台上的系統級編程語言,C++/CLI混合了Native和Managed兩種環境。而不象C#那樣只能進行托管編程。所以相對來說,C++/CLI可以具有更高的效率——前提是你願意把效率敏感的代碼用極具效率的Native C++來寫(當然,誰不願意呢?)另外,因為標准C++是靜態語言,所以作為標准C++的一個超集的C++/CLI能夠在編譯期得到更多的優化(靜態語言總是能夠得到更多的優化,因為編譯器能夠知道更多的信息),從而具有更高的效率。相比之下,C#的編譯期優化就弱了很多。
  l 混合式的編程環境——這是C++/CLI獨有的一種編程環境。你既可以進行高效的底層開發——對應於C++/CLI的標准C++子集,也可以在效率要求不那么嚴格的地方使用托管式編程以提高生產率。然后把兩者平滑的聯結在一起,而這一切都在你熟悉的編程語言中完成,你使用你熟悉的編程習慣,熟悉的庫,熟悉的語言特性和風格… 不需要從頭學習一門新的語言,不需要面對語言之間如何交互的問題。
  l 習慣——誰也不能小覷習慣的力量。對於標准C++程序員,如果要在.NET平台上開發,C++/CLI是毫無疑問的首選語言,因為他們在標准C++中積累起來的任何編程技巧,慣用法,以及對庫的使用經驗,代碼的表達方式等等全都可以“移植”到C++/CLI中。C++/CLI保持對標准C++代碼的完全兼容,同時以最小最一致的語法擴展提供托管環境下編程的必要語義。
  你需要改變什么?
   簡單的答案是,幾乎沒有什么需要改變的。是的,你看到“幾乎”兩個字,總有些不安心:o)事實是:把現存的C++代碼移植到C++/CLI環境下不用作任何的改變——我曾經用Native C++寫了一個程序,其中用到了STL,Boost里面的Lambda,MPL,Signal等庫,然后我把編譯選項“/clr”(甚至“/clr:pure”)打開,結果是程序完全通過了編譯。而對於使用C++/CLI進行開發的程序員,則需要熟悉的就是.NET平台上的編程范式以及庫的使用等,至於以前你所熟悉的標准C++編程的各種編程手法,技巧,各種庫的使用——Just Keep Them!
   所以,確切的說,你需要的是學習,而不是改變。
  C++/CLI——優秀的混血兒
   C++/CLI最大的成功在於引入了混合式編程的環境,這是一種非常自由的環境,其中Native和Managed代碼可以共存,可以相互溝通,從而完全接納了標准C++的世界,同時也為另一個世界敞開了大門...
   下面就是C++/CLI擴展的幾大關鍵特性——
  Handle和gcnew——通往Managed世界的鑰匙
   還記得在Managed C++ Extension世界里是如何訪問托管類的嗎?丑陋的__gc關鍵字無處不在——事實上,不僅是“丑陋”而已(MC++為什么會消亡?)。而在C++/CLI里則引入了一個新的語法元素,名為Handle,寫作“^”——你可以把它看成Managed世界里的Pointer(不過不能進行指針算術)。
  Handle用於持有Managed Heap上的對象,那么如何在Managed Heap上創建對象呢?原來的new顯然不能用,那樣會混淆其語義,所以C++/CLI引入了一個對應的gcnew關鍵字,這兩個新的語法元素是操縱Managed世界的關鍵。現在,使用Handle和gcnew,你就可以和任何托管類進行溝通。另外,既然有了Handle這個Managed指針,當然,基於另外一些重要原因,Managed世界里也要有一個和Native引用類似的語法元素——這就是Managed引用“%”——“^”對應“*”,“%”對應“&”,這樣一來,從語法的層面上,指針、引用、以及在堆上創建對象的語法就在兩個世界里面對稱一致了——哦,等等,還有解引用:對Native Pointer解引用是以“*”,出於模板對形式統一性的要求,對Handle解引用也是用“*”。例如:
  
  SomeManagedClass^ handle = gcnew SomeManagedClass( ... );
  handle->someMethod();
  SomeManagedClass% ref = *handle;
   那么,既然有gcnew,有沒有gcdelete呢?答案是沒有——雖然它們看起來很對稱。理由是對於托管類,根本就不用回收內存。但更為重要的還是,delete的語義不僅僅是回收內存,從廣義上說,delete是回收資源的意思,從這個意義上,delete托管類還是Native類的對象都是一個意思。所以,即使你需要delete你的托管類對象,以強制其釋放資源,你也應該用delete,這時候托管類的析構函數會被調用——是的,托管類也有析構函數,它的語義和Dispose()一樣,但是在C++/CLI里面,你不應該為你的托管類定義Dispose()函數,而總是應該用析構函數來代替它(編譯器會根據析構函數自動生成Dispose()函數),因為析構函數有一個最大的優點——
  Deterministic Destruction & RAII —— 資源管理的利器
  正如每一個熟悉標准C++的程序員所清楚的:由C++構造及析構函數的語義保證所支持的RAII(“資源獲取即初始化”[2])技術是資源自動和安全管理的利器,這里的資源可以包括內存,文件句柄,mutex,lock等。通過正確的使用RAII,管理資源的代碼可以變得驚人的優雅和簡單。相信有經驗的C++程序員都熟悉應該類似下面的語句:
  
  void f()
  {
   ofstream outf(“out.txt”);
   out<<”...”;
   ...
  } //outf在這里析構!
   這里,程序員根本不用手動清理outf,在函數結束(outf超出作用域)時,outf會自動析構,並釋放其所有資源。即使后續的代碼拋出了異常,C++語言也能保證析構函數會被調用。事實上,在異常拋出后,棧開解(stack unwind)的過程中,所有已經正確構造起來的局部對象都會被析構。這就為異常環境中資源的管理提供了一種強大而優雅的方式。
   而對於C#或Java,代碼就沒有這么優雅了(特別是java)——C#雖然有using關鍵字,但是代碼仍然顯得臃腫,而Java為了保證在異常情況下資源能夠正常釋放,不得不用了丑陋冗長的try-finally塊,在情況變得復雜化時,C#的和Java的代碼都會變得越發臃腫。
   那么,在C++/CLI中,原來的那種優雅的,靠析構函數來確保資源正確釋放的手段還存在嗎?答案正如你所期望和熟悉的,RAII仍然可以使用,仍然和標准C++中的能力一樣強大:
  
  ref struct D
  {
  D(){System::Console::WriteLine(“in D::D()\n”);}
  ~D(){System::Console::WriteLine(“in D::~D()\n”);}
  !D(){System::Console::WriteLine(“Finalized!\n”);}
  };
  int main()
  {
   D d; // in D::D()
   ...
  } //d在這里析構!in D::~()
   ref關鍵字表示該類是Managed類。所有的ref類都繼承自一個公共基類System::Object。至於struct和class的區別仍然和標准C++中的一樣。如你所見,對於ref類,你同樣可以像在標准C++中那樣定義析構函數,該析構函數會在確定的時候被調用——也就是D超出作用域時。一切都與你以前的經驗相符。
   值得注意的是,對於了解Java或C#的程序員,ref類的析構函數就是Dispose(),你不必也不應該另外手動定義一個Dispose()成員函數。那么,Finalize函數到那里去了?既然ref類創建在托管堆上,那么遲早要被GC回收,這時候,應該被調用的Finalize函數在哪兒呢?C++/CLI為此引入了一個新的語法符號“!D()”,這就是D的Finalize函數,這個“!D”函數被調用的時機是不確定的,要看GC什么時候決定回收該類占用的空間。
   ~D()析構函數和標准C++里的用法完全相同,釋放以前獲取的資源。而對!D()的用法則和Finalize函數一樣,由於其調用時機是不確定的,所以千萬不要依賴於它來釋放關鍵資源(如文件句柄,Lock等)。
   為ref類引入~D()和!D()極大的方便了資源管理,也符合了標准C++程序員所熟悉的方式。Herb Sutter[3]把這個能力看成C++/CLI在Managed環境下最為強大的能力之一。
  pin_ptr —— 定身法
  千萬不要小看了pin_ptr的能力,它是Native世界和Managed世界之間的橋梁。在通常情況下,任何時候,GC都會啟動,一旦進行GC,托管堆就會被壓縮,對象的位置就會被移動,這時候所有指向對象的Handle都會被更新。但是,往往有時候程序員會希望能夠把托管堆上的數據(的地址)傳給Native接口,比如,為了復用一個Native的高效算法,或者為了高效的做某些其它事情,這種情況下普通的Native指針顯然不能勝任,因為如果允許Native指針指向托管堆上的對象,那么一旦發生了GC,這些得不到更新的Native指針將指向錯誤的位置,造成嚴重的后果。辦法是先把對象“定”在Managed堆上,然后再把地址傳給Native接口,這個“定身法”就是pin_ptr——它告訴GC:在壓縮堆的時候請不要移動該對象!
  
  array<char>^ arr = gcnew array<char>(3); //托管類
  arr[0] = 'C';
  arr[1] = '+';
  arr[2] = '+';
  pin_ptr<char> p = &arr[0]; // 整個arr都被定在堆上
  char* pbegin=p;
  std::sort(pbegin,pbegin+3); //復用Native的算法!
  std::cout<<pbegin[0]<<pbegin[1]<<pbegin[2]; //輸出 “++C”
   在上面的代碼中,我們復用了STL里的sort算法。事實上,既然有了pin_ptr,我們可以復用絕大部分的Native算法。這就為我們構建一個緊湊高效的程序內核提供了途徑。
   值得注意的是,一旦對象中的成員被定在了堆上,那么該對象整個就被定在了堆上——這很好理解,因為對象移動必然意味着其成員的移動。
   還有另一個值得注意的地方就是:pin_ptr只能指向某些特定的類型如基本類型,值類型等。因為這些類型的內存布局都是特定的,所以對於Native代碼來說,通過Native指針訪問它們不會引起意外的后果。但是,ref class的內存布局是動態的,CLR可以對它的布局進行重整以做某些優化(如調整數據成員排布以更好的利用空間),從而不再是Native世界所能理解的靜態結構。然而,這里最主要的問題還是:ref class底層的對象模型和Native世界的對象模型根本就不一致(比如vtbl的結構和vptr的位置),所以用Native指針來接受一個ref class實例的地址並調用它的方法簡直肯定是一種災難。由於這個原因,編譯器嚴格禁止pin_ptr指向ref class的實例。
  interior_ptr —— 托管環境下的Native指針
   Handle的缺憾是不能進行指針運算(由於其固有的語義要求,畢竟Handle面對的是一個要求“安全”的托管環境),所以Handle的能力較為有限,不如標准C++程序員所熟悉的Native指針那么強大。在STL中,iterator是一種極為強大也極具效率的工具,其底層實現往往用到Native指針。而到了托管堆上,我們還有Native指針嗎?當然,原來的形如T*的指針是不能再用了,因為它不能跟蹤托管堆上對象的移動。所以C++/CLI中引入了一種新的指針形式——interior_ptr。interior_ptr和Native指針的語義幾乎完全一樣,只不過interior_ptr指向托管堆,在GC時interior_ptr能夠得到更新,除此之外,interior_ptr允許你進行指針運算,允許你解引用,一切和Native指針並無二致。interior_ptr為你操縱托管堆上的數據序列(如array)提供了強大而高效的工具,iterator模式因此可以原版照搬到托管環境中,例如:
  
  template<typename T>
  void sort2(interior_ptr<T> begin,interior_ptr<T> end)
  {
   ... //排序算法
   for(interior_ptr<T> pn=begin;pn!=end;++pn)
   {
   System::Console::WriteLine(*pn);
   }
  }
  int main()
  {
  array<char>^ arr = gcnew array<char>(3);
   ... //賦值
   interior_ptr<char> begin = &arr[0]; //指向頭部的指針
   interior_ptr<char> end = begin + 3; //注意,不能寫&arr[3],會下標越界
   sort2(begin,end); //類似STL的排序方式!
  }
  T*,pin_ptr,interior_ptr——把它們放到一起
   T*,pin_ptr,interior_ptr是C++/CLI中三種最為重要的指針形式。它們之間的關系像這樣:
  
  強大的Override機制
   在標准C++中,虛函數重寫機制是隱式的,只要兩個函數的簽名(Signature)一樣,並且基類的同名函數為虛函數,那么不管派生類的函數是否為virtual,都會發生虛函數重寫。某種程度上,這就限制了用戶對它的派生類的控制能力——虛函數的版本問題就是其一。而在C++/CLI中,你擁有最為強大的override機制,你可以更為明顯的來表示你的意圖,例如下面的代碼:
  
  class B
  {
  public:
   virtual void f() ;
   virtual void g() abstract; //純虛函數,需要派生類重寫,否則派生類就是純虛類
   virtual void h() sealed; //阻止派生類重寫該函數
   virtual void i() ;
  }
  class D:public B
  {
   virtual void f() new ; //新版本的f,雖然名字和B::f相同,但是並沒有重寫B::f。
   virtual void h() override ; //錯誤!sealed函數不能被重寫
   virtual void k() = B::i ; //“命名式”重寫!
  }
   通過正確的使用這些強大的override機制,你可以獲得對類成員函數更強大的描述能力,避免出乎意料的隱式重寫和版本錯誤。不過需要提醒的是,“命名式”重寫是一種強大的能力,但是需要謹慎使用,如果使用不當或濫用很可能導致名字錯亂。
  值類型&封箱和拆箱
   如果你來自C#,我幾乎可以聽到你的嘆氣聲J 的確,在.NET平台上編程,你無可避免的要面對值類型和引用類型的微妙差別以及“瘋狂”的隱式封箱——引用類型(對應於ref class)的實例是第一流的對象,繼承自公共基類System::Object,擁有方法表,對象頭等等。但是值類型(對應於value class)卻極為簡單,類似於C++中的POD[4]類型,沒有方法表和對象頭等,值類型應該被分配在棧上,而當你用Handle來持有值類型實例時,它就會被隱式的封箱到托管堆上(因為Handle必須持有一個一流的對象),只有當值類型的實例被封箱到堆上的時候,它才會擁有第一流的對象特征,可以被Object^來引用。
   這些都是.NET內在的特性,所有使用.NET平台的語言都必須遵守,從這個意義上說,.NET的確是最高統治者J
   幸運的是,情況或許沒有你想象的那么糟糕,或許比在C#里面還要好一些——因為C++/CLI中的Handle的語法特征是如此明顯,所以你幾乎可以立即發現什么地方會出現封箱拆箱(盡管如此,還是要面對一些微妙的情況),我們來看一個例子:
  
  value class V //value關鍵字表示這是個值類型,值類型應該分配在棧上
  { int i;};
  V v; //在棧上創建V的實例
  //由於V^必須引用一個“完整”的對象,也就是具有方法表,元數據以及對象頭並繼承自System::Object公共基類的對象,所以v被隱式封箱到托管堆上。
  V^ hv1 = v; //注意,隱式封箱!
  V^ hv2 =%v; //也是封箱!把”%”用到值類型上會導致一個Handle,
   //所以會封箱,這種形式比較明確!
  hv1->i = 10; //改變的不過是堆上封箱后的對象中的i,v的成員i的值並未改變
  v = *hv1; //unbox,然后逐位拷貝到棧上,這時候v.i為10
   這里你可能意識到了問題——既然用Handle來持有值類型總會導致它被封箱到托管堆上,那么萬一我要寫一個函數,接受一個(棧上的)值類型實例為實參並改變其成員的值,該怎么辦呢?如果使用Handle,那么你所指向的就不是原來的值而是封箱后的對象,從而看起來改變了其成員,其實只不過改變了一個“臨時”對象的值而已!所以,Handle在這里應該退居二線,這里是“%”(托管的引用,對應於Native引用——“&”)的用武之地——把一個托管引用綁定到位於棧上的值類型不會引起封箱操作,我們看一個例子:
  
  void adjust(V% ref_v)
  {
   ref_v.i = 10; //改變ref_v的成員!
  }
  int main()
  {
   V v;
   adjust(v); //不會引起封箱操作
   System::Console::WriteLine(v.i); //打印出10
  }
   原則是:要修改棧上的值類型實例,優先使用“%”,而不是“^”。這樣你將獲得最好的效率和程序的正確性。
  STL.NET
   STL是標准C++中最為優雅,使用最廣泛的庫之一,標准C++程序員在使用STL的過程中積累了大量的經驗。當然,在C++/CLI的擴展世界里,人們也期望能有這樣的庫,能夠沿用他們熟悉以久的經驗和技法,這就是STL.NET,為托管世界准備的STL!Stan Lippman[5]在MSDN上的一篇文展STL.NET Primer以簡明扼要的方式闡述了STL.NET的優點[6]
  代碼的組織
   雖然C++/CLI帶來了強大的能力,但是對於從標准C++社群來的人們,則更願意將他們的標准C++代碼和使用了C++/CLI擴展特性的代碼隔離開來,以便讓前者可以在不同平台上移植,而不是綁定到CLI平台。畢竟,用C++/CLI編程並不意味着你的所有代碼都是和C++/CLI的擴展特性相關的——C++/CLI的定位是系統級編程,所以可以想象會有很大一部分人會非常願意用標准C++來寫效率關鍵的代碼部分,例如你可以用標准C++來寫高效的算法,而這些算法應該可以被復用到其它Native環境中去。那么,如何把這些標准C++代碼和C++/CLI的擴展特性隔離開來呢?如何隔離?不同編譯單元之間的界限就是最好的柵欄——把你的標准C++代碼放在獨立的頭文件和源文件中,把使用了C++/CLI擴展的代碼放在另外的頭文件和源文件中。並且,盡量不要在你的Native class中使用CLI的語法特性,如property,delegate,index等,盡量不要讓你的Native Class繼承自ref Class。總之,盡量保證代碼結構的清晰,你將得到最大程度上的可移植性。
  小結
   C++/CLI是一個創舉,它把托管環境和Native環境整合在一起,使開發者同時擁有了“上天入地”的強大能力。顯而易見,微軟為了C++/CLI花費了大量的心力。以使得標准C++程序員能夠平滑的過渡到C++/CLI上面。所謂平滑,就是能夠盡量保證原來的編程技巧,習慣,范式等,它的確做到了。面對C++/CLI,已經不是爭論該不該學習的問題,而是如何讓它發揮更大的能量的問題。
  
[1] C++/CLI標准下載:http://msdn.microsoft.com/visualc/homepageheadlines/ecma/default.aspx
[2] 見Bjarne Stroustrup的《The C++ Programming Language》
[3] Herb Sutter’s Blog: http://www.pluralsight.com/blogs/hsutter/default.aspxhttp://blogs.msdn.com/hsutter/
[4] Plain Old Data類型,簡單的說就是純粹一集數據的聚合體。沒有虛函數,構造函數,析構函數等“C++”特性。
[5] Stan Lippman的 Blog: http://blogs.msdn.com/slippman
[6] STL.NET Primer:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/stl-netprimer.asp

來自:http://www.wangchao.net.cn/bbsdetail_56129.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM