zoukankan      html  css  js  c++  java
  • CLR指针

    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_ptr。interior_ptr和Native指针的语义几乎完全一样,只不过interior_ptr指向托管堆,在GC时interior_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_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机制,你可以获得对类成员函数更强大的描述能力,避免出乎意料的隐式重写和版本错误。不过需要提醒的是,“命名式”重写是一种强大的能力,但是需要谨慎使用,如果使用不当或滥用很可能导致名字错乱。

  • 相关阅读:
    linux中如何修改文件夹的用户权限 chown命令
    httpserver
    协程
    进程和线程的区别和联系
    python线程的GIL问题(全局解释器锁)
    线程同步互斥的方法
    threading模块创建线程
    信号量(信号灯)
    信号通道
    内存共享
  • 原文地址:https://www.cnblogs.com/Daywei/p/2639645.html
Copyright © 2011-2022 走看看