分享

将程序从托管扩展 C 迁移到 C /CLI(2)

 donixli1314 2008-10-23
2.2 一个 CLI 的引用类对象的声明
在原版语言定义中,一个引用类类型对象是使用 ISO-C++指针语法声明的,在星号左边使用可选的 __gc关键字。例如,以下是 V1语法下多种引用类类型对象的声明:

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(); // ... } }; 在修订版语言设计中,引用类类型的对象用一个新的声明性符号(^)声明,正式的表述为跟踪句柄,不正式的表述为帽子。(跟踪这个形容词强调了引用类型对象位于 CLI堆中,因此可以透明地在垃圾回收堆的压缩过程中移动它的位置。一个跟踪句柄在运行时被透明地更新。两个类似的概念:(a)跟踪引用(%) 和 (b)内部指针(interior_ptr<>),在第4.4.3节讨论。

声明语法不再重用 ISO-C++指针语法有两个主要原因:

1.
指针语法的使用不允许重载的操作符直接应用于引用对象;而必须通过其内部名称调用操作符,例如 rV1->op_Addition (rV2) 而不是更加直观的 rV2+Rv2。

2.
有许多指针操作,例如类型强制转换和指针算术对于位于垃圾回收堆上的对象无效。我们认为一个跟踪句柄的概念最好符合一个 CLI 引用类型的本性。


对一个跟踪句柄使用 __gc修饰符是不必要的,而且是不被支持的。对象本身的用法并未变化,它仍旧通过指针成员选择操作符 (->) 访问成员。例如,以下是上面的 V1文字转换到新语言语法的结果:

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(); // ... } }; 2.2.1在CLI堆上动态分配对象

在原版语言设计中,现有的在本机堆和托管堆上分配的两种 new表达式很大程度上是透明的。在几乎所有的情况下,编译器能够从上下文正确地确定所需的是本机堆还是托管堆。例如:

Button *button1 = new Button; // OK: 托管堆 int *pi1 = new int; // OK: 本机堆 Int32 *pi2 = new Int32; // OK: 托管堆 在上下文堆分配并非所期望的实例时,可以用 __gc或者 __nogc关键字指引编译器。在修订版语言中,使用新引入的 gcnew关键字来显示两个 new 表达式的不同本质。例如,上面三个声明在修订版语言中如下所示:

Button^ button1 = gcnew Button; // OK: 托管堆 int * pi1 = new int; // OK: 本机堆 interior_ptr pi2 = gcnew Int32; // OK: 托管堆 (在第 3 节中讨论interior_ptr的更多细节。通常,它表示一个对象的地址,这个对象可能(但不必)位于托管堆上。如果指向的对象确实位于托管堆上,那么它在对象被重新定位时被透明地更新。)

以下是前面一节中声明的 Form1成员V1版本的初始化:

void InitializeComponent() { components = new System::ComponentModel::Container(); button1 = new System::Windows::Forms::Button(); myDataGrid = new DataGrid(); button1->Click += new System::EventHandler(this, &Form1::button1_Click); // ... } 以下是用修订版语法重写的同样的初始化过程,注意引用类型是一个 gcnew表达式的目标时不需要“帽子”。

void InitializeComponent() { components = gcnew System::ComponentModel::Container; button1 = gcnew System::Windows::Forms::Button; myDataGrid = gcnew DataGrid; button1->Click += gcnew System::EventHandler( this, &Form1::button1_Click ); // ... } 2.2.2无对象的跟踪引用

在新的语言设计中,0不再表示一个空地址,而仅被处理为一个整型,与 1、10、100一样,这样我们需要引入一个特殊的标记来代表一个空值的跟踪引用。例如,在原版语言设计中,我们如下初始化一个引用类型来处理一个无对象:

//正确:我们设置 obj 不引用任何对象 Object * obj = 0; //错误:没有隐式装箱 Object * obj2 = 1; 在修订版语言中,任何从值类型到一个 Object的初始化或者赋值都导致一个值类型的隐式装箱。在修订版语言中,obj和 obj2都被初始化为装箱过的 Int32对象,分别具有值 0和 1。例如:

//导致 0 和 1 的隐式装箱 Object ^ obj = 0; Object ^ obj2 = 1; 因此,为了允许显式的初始化、赋值,以及将跟踪句柄与空进行比较,我们引入了一个新的关键字 nullptr。这样 V1示例的正确版本如下所示:

//OK:我们设置 obj 不引用任何对象 Object ^ obj = nullptr; //OK:我们初始化 obj 为一个 Int32^ Object ^ obj2 = 1; 这使得从现存 V1代码到修订版语言设计的移植更加复杂。例如,考虑如下值类声明:

__value struct Holder { //原版 V1 语法 Holder( Continuation* c, Sexpr* v ) { cont = c; value = v; args = 0; env = 0; } private: Continuation* cont; Sexpr * value; Environment* env; Sexpr * args __gc []; }; 这里 args和 env都是CLI引用类型。在构造函数中将这两个成员初始化为 0的语句在转移到新语法的过程中必须修改为 nullptr:

//修订版 V2 语法 value struct Holder { Holder( Continuation^ c, Sexpr^ v ) { cont = c; value = v; args = nullptr; env = nullptr; } private: Continuation^ cont; Sexpr^ value; Environment^ env; array^ args; }; 类似的,将这些成员与 0进行比较的测试也必须改为和 nullptr比较。以下是原版的语法:

// 原版 V1 语法 Sexpr * Loop (Sexpr* input) { value = 0; Holder holder = Interpret(this, input, env); while (holder.cont != 0) { if (holder.env != 0) { holder=Interpret(holder.cont,holder.value,holder.env); } else if (holder.args != 0) { holder = holder.value->closure()-> apply(holder.cont,holder.args); } } return value; } 而以下是修订版语法。将每个 0实例转换为 nullptr。(转换工具有助于这个转换,进行许多自动处理 — 如果不是全部出现,包括使用 NULL宏。)

//修订版 V2 语法 Sexpr ^ Loop (Sexpr^ input) { value = nullptr; Holder holder = Interpret(this, input, env); while ( holder.cont != nullptr ) { if ( holder.env != nullptr ) { holder=Interpret(holder.cont,holder.value,holder.env); } else if (holder.args != nullptr ) { holder = holder.value->closure()-> apply(holder.cont,holder.args); } } return value; } nullptr可以转化成任何跟踪句柄类型或者指针,但是不能提升为一个整数类型。例如,在如下初始化集合中,nullptr只在开头两个初始值中有效。

//正确:我们设置 obj 和 pstr 不引用任何对象 Object^ obj = nullptr; char* pstr = nullptr; //在这里用0也可以 //错误:没有从 nullptr 到 0 的转换 ... int ival = nullptr; 类似的,给定一个重载过的方法集,如下所示: void f( Object^ ); // (1) void f( char* ); // (2) void f( int ); // (3) 一段使用 nullptr的调用如下所示:

// 错误:歧义:匹配 (1) 和 (2) f( nullptr ); 是有歧义的,因为 nullptr既匹配一个跟踪句柄也匹配一个指针,而且在两者中没有一个优先选择(这需要一个显式的类型强制转换来消除歧义)。

一个使用 0的调用正好匹配实例 (3):

//正确:匹配 (3) f( 0 ); 由于 0是整型。当没有 f(int)的时候,调用会通过一个标准转换无歧义地匹配f(char*)。匹配规则优先于标准转换的精确匹配。在没有精确匹配时,标准转换优先于对于值类型的隐式装箱。这就是没有歧义的原因。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多