【托管引用】C++/CLI中的引用类型

 

 

ref class R
{
private:
   int x;
public:
   R(int xx): x(xx) {}
};

R^ o = gcnew R(3);  //在托管堆
R os(3);   //也在托管堆
o 和 os 之间的区别在它们的生存期上,或者说得更加具体一些,是对它们生存期的控制力。



 如果编写的是托管代码,你可能不会介意放弃对内存的控制权,反而愿意信任运行库和垃圾回收器为你管理内存。
但是开发人员仍然需要操心与内存无关的清除工作: 比如关闭文件或者连接。垃圾回收本身不足以处理你在应用程序中使用的所有资源。
 在 C++ 中,这种与内存无关的清除通常是在析构函数中进行的。

 

托管堆中的对象是通过句柄 o 访问的,当控制达到带有 gcnew 的那一行时对象就开始存在。
未来某个时候, o 将超出控制范围。 可能控制已经超过用 return 或者 exit 语句声明它的代码块,
可能代码块是 if, for, 或者 while 语句的谓词而且控制已经以通常的方式离开,或者出现了异常。
无论原因如何, o 都将超出范围。 这时候,事情变得有些复杂。 如果任何代码都有句柄的副本,
副本将到处都是,然后只要范围中有句柄,对象就将继续在托管堆中存在。 如果句柄对象应该回收了,
但是回收的准确时间并不知道,因此何时运行析构函数是未知的。 这取决于应用程序施加的内存压力数量等等因素。

对于堆栈中的对象 os ,情况就大大不同了。 在超出范围后(按照使 o 超出范围的同样情况),对象的一切就结束了。
它的析构函数,如果有的话,将在 os 离开范围后立即运行。你可以准确地知道与内存无关的清除何时发生,而且能够尽快发生。这就是所谓确定性析构。

顺便提及, os 实例(我们认为它在堆栈中)实际上使用的是托管堆上的内存(依然是由垃圾回收器托管的)。
析构函数并不回收该实例使用的内存;它关心的是与内存无关的清除。 引用类型只能模拟为在堆栈中。
 如果你已经习惯不管内存管理,并信任垃圾回收器处理一切,这种模拟是非常理想的。

    -----------《C/C++ 应用程序路线图》


p96
R^ o = gcnew R(3);
句柄o存储在栈上,gcnew的R实际对象存储在托管堆上。

当一个托管引用被声明为存在于堆栈上时,编译器实际上还会在托管堆上对其进行实例化
    -----------《使用 Visual C++ 2005 的现代语言功能编写更快的代码》
os是栈中对象,但编译器将其在托管堆中实例化。

引用类型永远分配到托管堆中,值类型相比引用类型小得多,被分配在线程栈上。值类型不受垃圾回收控制。
在文档中...class 都是引用类型,如System.Object class, the System.Exception class, the
System.IO.FileStream class, and the System.Random class
在文档中...struct或...enum都是值类型,如System.Boolean structure, the System.Decimal structure, the
System.TimeSpan structure, the System.DayOfWeek enumeration, the System.IO.FileAttributes, enumeration, and the System.−Drawing.FontStyle enumeration
    -----------《Microsoft.NET Framework Programming》p114
R是引用类型,因此无论是gcnew的对象,还是对象os,都是存储在托管堆上。

 

 

值类型默认存储在栈中,但是可以使用gcnew使其分配在托管堆中。
    -----------《Beginning Visual C++ 2005》p199
引用类型只保存在托管堆中
值类型有双重属性,其装箱后保存在托管堆中,正常时保存在栈中。
    -----------《C++CLIRationale》p26
值类型是不支持垃圾回收的,但如果gcnew分配后,就支持垃圾回收了??

int^ value = 99;在托管堆中分配内存。
    -----------《Beginning Visual C++ 2005》p200

 

我的理解::引用类型对象都在托管堆,^句柄所指的内存都是托管堆,^句柄在栈中

 

o 和 os 之间的区别的例子。o可以不随着范围的结束而清除。os随着范围的结束而清除。
 R^ o;
 dd = 1;
 while(dd==1){
  o = gcnew R(3);   //在托管堆
  R os(3);   //也在托管堆
  R^ nn = o;
//  R^ n1 = %os;
  dd--;
 }
由此可以知道,引用类型不管是不是使用了gcnew创建的对象,都分配在托管堆中。
引用类型由CLR支持,对象包含元数据信息。
int^ aa;
也分配到托管堆中。是在托管堆中存储的句柄。
int* pM;在堆栈中的指针pM,无法指向托管堆中的内存。如
 int ^a5 = gcnew int(5);
 int *pM;
 pM = a5;
不会通过编译。
 int ^a5;
 int *pM;
 pM = a5;
即使不使用gcnew分配内存,a5仍然是托管堆中的句柄,pM仍然无法指向它。也不会通过编译。


将无元数据信息的数据类型,转化为包含元数据信息的引用类型,这一过程称为装箱。即把简单的对象放到箱子里,使之成为复杂对象。
相反的过程称为拆箱。

 

最后,对引用类型、值类型、和非托管类的内存分配位置作了一个表格

 

ref class

value class

class

T *t = new T;

-----

本地堆

本地堆

T ^t = gcnew T; 

托管堆

托管堆

-----

T t;

栈(托管堆)

 

原文: http://blogs.msdn.com/b/oliverlu/archive/2004/12/28/333143.aspx

 

这就是非托管类型:

class Foo
{
   private:
      int x;
   public:
      Foo(): x(0){}
      Foo(int xx): x(xx) {}
};

这就是托管类型

__gc class Bar
{
   private:
      int x;
   public:
      Bar(): x(0){}
      Bar(int xx): x(xx) {}
};

他们唯一的区别就是类Bar的定义中有__gc关键字。这个关键字会给代码带来巨大的区别。

托管类型是可以被垃圾回收器所回收的。他们必须要用关键字new来创建,永远都不会在栈中出现。所以下面这行代码是合法的:

    Foo f;

但是这一行代码就是非法的:

    Bar b;

如果我在堆中创建一个Foo对象,那么我必须要负责清理这个对象:

    Foo* pf = new Foo(2);
    // . . .
    delete pf;

C++编译器实际上会用两个堆,一个托管堆和一个非托管堆,然后通过对new操作符的重载来实现对创建不同类型类的实例,分配不同的内存。

如果我在堆里面创建一个Bar实例,那么我可以忽略它。当没有其他代码在使用它的时候,垃圾回收器会自动清理这个类,释放其占用的资源。

对于托管类型会有一些约束:它们不能实现多重继承,或者继承与非托管类型;它们不能用friend关键字来实现私有访问,它们不能实现拷贝构造函数。所以,你有可能不想把你的类声明为托管类型。但是这并不意味着你不想让你的代码成为托管代码。在Visual C++中,你可以选择。

原文:http://www.yeeyan.org/articles/view/maguschen/23927

 

 

 

 

   创建了托管类型的对象,就要有指向他们的指针。这里是刚开始学习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

 

http://blogs.msdn.com/b/oliverlu/archive/2004/12/28/333143.aspx

 

posted on 2022-10-04 01:30  bdy  阅读(198)  评论(0编辑  收藏  举报

导航