從C++到C++/CLI

來源:互聯網
上載者:User

  劉未鵬(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.aspx或http://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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.