从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机制,你可以获得对类成员函数更强大的描述能力,避免出乎意料的隐式重写和版本错误。不过需要提醒的是,“命名式”重写是一种强大的能力,但是需要谨慎使用,如果使用不当或滥用很可能导致名字错乱。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值