從C++到C++/CLI(3)

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<輸出 “++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_ptrinterior_ptrNative指針的語義幾乎完全一樣,只不過interior_ptr指向託管堆,在GCinterior_ptr能夠得到更新,除此之外,interior_ptr允許你進行指針運算,允許你解引用,一切和Native指針並無二致。interior_ptr爲你操縱託管堆上的數據序列(如array)提供了強大而高效的工具,iterator模式因此可以原版照搬到託管環境中,例如:

 

 

template<typename T>

 

void sort2(interior_ptr begin,interior_ptr end)

 

{

 

    ... //排序算法

 

    for(interior_ptr 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_ptrinterior_ptr——把它們放到一起

 

       T*pin_ptrinterior_ptrC++/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機制,你可以獲得對類成員函數更強大的描述能力,避免出乎意料的隱式重寫和版本錯誤。不過需要提醒的是,“命名式”重寫是一種強大的能力,但是需要謹慎使用,如果使用不當或濫用很可能導致名字錯亂。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章