1. 程式人生 > >C++/CLI學習筆記1—新的託管型別

C++/CLI學習筆記1—新的託管型別

感謝Stanley B. Lippman為我們提供了一篇非常優秀的關於C++/CLI的介紹:

Translation Guide: Moving Your Programs from Managed Extensions for C++ to C++/CLI

儘管我的英文不是很好,有時候沒有辦法理解Lippman先生的原意,但我還是非常欣賞這篇文章,因為寫得非常深入與詳細。

我很想做些筆記,加深理解,所以就辦抄半簡地寫了這些中文帖子。我覺得自己已經開始喜歡上C++/CLI了,希望大家也一樣喜歡他。

Managed C++中令人印象深刻的就是__gc這樣的形式在開始有兩個下滑線。其實,原先這樣設計的目的,也是為了

Managed C++和標準C++儘量相同--畢竟標準C++中可沒有什麼“託管”的概念。但從幾年來的結果來看,似乎效果非常不理想。大家已經忘了Visual Studio.NET中還有C++。所以,在C++/CLI中,其實是對標準C++作了一個擴充套件。儘管多了一些從來沒有看到過的操作符,但沒有了__gc這樣的關鍵詞,而且看慣了新的操作符以後,會發現這樣其實更加自然,也更接近標準C++。其實,這也正是C++/CLI設計的目的之一吧。

我們來看看C++/CLI中是怎樣來定義託管型別的:

如果要建立一個對於在託管堆中物件的應用(reference class),我們可以使用下面的兩個關鍵詞:

ref class

ref struct

其中,struct意味著預設情況下它的成員的訪問級別為public,而class意味著預設情況下它的成員的訪問級別為private

如果要建立值型別(value class),那麼可以使用下面的關鍵詞:

value class

value struct

同樣,對於介面,關鍵詞為interface class

所以,我們就可以看到這樣的定義:

public ref class Block { ... };

public value class Vector { ... };

public interface class IMyFile { ... };

如果我們需要指定一個抽象

(Abstract)的類,那麼語法為:

public ref class Shape abstract {};

public ref class Shape2D abstract : public Shape{};

也可以指定為Sealed型別,以及SealedAbstract共用:

public ref class String sealed {};

public ref class State abstract sealed{};

建立了託管型別的物件,就要有指向他們的指標。這裡是剛開始學習C++/CLI的時候,讓人有些不習慣的地方,因為定義了一個全新的操作符^

我們可以先來看看原來的_gc *和新的^的一些具體例子,然後再來感覺那個更好一些:

public __gc class Form1 : public System::Windows::Forms::Form {

private:

   System::ComponentModel::Container __gc *components;

   Button __gc *button1;

   DataGrid __gc *myDataGrid;  

   DataSet __gc *myDataSet;

void PrintValues( Array* myArr ) 

{

    System::Collections::IEnumerator* myEnumerator =

myArr->GetEnumerator();

          Array *localArray = myArr->Copy();

          // ...

      }

   };

public ref class Form1: public System::Windows::Forms::Form{

private:

   System::ComponentModel::Container^ components;

   Button^ button1;

   DataGrid^ myDataGrid;

   DataSet^ myDataSet;

void PrintValues( Array^ myArr )

{

          System::Collections::IEnumerator^ myEnumerator =

                myArr->GetEnumerator();

          Array ^localArray = myArr->Copy();

             // ...

      }

   };

是不是^更加自然呢?

可能會有人問:為什麼不使用C++中原來的指標符號*呢?呵呵,因為這是託管程式碼啊!如果物件定義在非託管堆上,當然使用*了,但如果定義在託管堆上,就不能再簡單地使用*了。我們可以看看下面的例子:

Button^ button1 = gcnew Button;        // OK: managed heap

int * pi1 = new int;                   // OK: native heap

interior_ptr<Int32> pi2 = gcnew Int32; // OK: managed heap

這裡說明兩點:

1. C++/CLI中,同時使用gcnew,來說明物件是建立在託管堆上的。

2. 關於interior_ptr,留待後面解釋。

同時,在C++/CLI中,數字0不再代表空地址,而僅僅代表數字0。空地址有一個關鍵字:nullptr。所以,下面的程式碼就是把0boxing了,再傳給指標obj

Object^ obj=0;

而只有下面的語句,才真正把一個空指標給指標obj:

Object^ obj=nullptr;

順便,談談boxing。boxing就是說,當你想把一個值(value)當作一個物件(object)的時候,發生的事情。例如上面的語句中,就是把一個值0變成了一個object物件。具體在後臺發生的事情為:

1. 首先,這個值會被壓入堆疊。

2. CLR接著會把這個值彈出堆疊,然後分配一塊空間,來儲存這個值以及物件頭資訊。

3. 接著,一個新建立的物件的引用,會被壓入堆疊中。

4. 最後,把這個引用物件彈出堆疊,儲存在本地變數中。

可見,代價非常大。所以,一般情況下,不要隨便使用boxing(當然還有unboxing),因為對效能影響太大。

CLI陣列的定義

C++/CLI中,陣列定於為:

void PrintValues( array<Object^>^ myArr );

void PrintValues( array<int,3>^ myArr );

同時,還可以使用gcnew定義的時候,直接賦值:

array<Object^>^ myArray =

      gcnew array<Object^>(4){ 1, 1, 2, 3 }

而且,還可以定義返回型別為CLI陣列的函式:

array<Int32>^ f();

array<int>^ GetArray();