从C++到C++/CLI (2)

Deterministic Destruction & RAII —— 资源管理的利器

 

正如每一个熟悉标准C++的程序员所清楚的:由C++构造及析构函数的语义保证所支持的RAII(“资源获取即初始化”[2])技术是资源自动和安全管理的利器,这里的资源可以包括内存,文件句柄,mutexlock等。通过正确的使用RAII,管理资源的代码可以变得惊人的优雅和简单。相信有经验的C++程序员都熟悉应该类似下面的语句: 

 

void  f() 

       ofstream outf(“
out .txt”); 
       
out << ”...”; 
     ... 

// outf在这里析构!

       这里,程序员根本不用手动清理outf,在函数结束(outf超出作用域)时,outf会自动析构,并释放其所有资源。即使后续的代码抛出了异常,C++语言也能保证析构函数会被调用。事实上,在异常抛出后,栈开解(stack unwind)的过程中,所有已经正确构造起来的局部对象都会被析构。这就为异常环境中资源的管理提供了一种强大而优雅的方式。

 

       而对于C#Java,代码就没有这么优雅了(特别是java)——C#虽然有using关键字,但是代码仍然显得臃肿,而Java为了保证在异常情况下资源能够正常释放,不得不用了丑陋冗长的try-finally块,在情况变得复杂化时,C#的和Java的代码都会变得越发臃肿。

 

       那么,在C++/CLI中,原来的那种优雅的,靠析构函数来确保资源正确释放的手段还存在吗?答案正如你所期望和熟悉的,RAII仍然可以使用,仍然和标准C++中的能力一样强大:

       ref关键字表示该类是Managed类。所有的ref类都继承自一个公共基类System::Object。至于structclass的区别仍然和标准C++中的一样。如你所见,对于ref类,你同样可以像在标准C++中那样定义析构函数,该析构函数会在确定的时候被调用——也就是D超出作用域时。一切都与你以前的经验相符。

 

     值得注意的是,对于了解JavaC#的程序员,ref类的析构函数就是Dispose(),你不必也不应该另外手动定义一个Dispose()成员函数。那么,Finalize函数到那里去了?既然ref类创建在托管堆上,那么迟早要被GC回收,这时候,应该被调用的Finalize函数在哪儿呢?C++/CLI为此引入了一个新的语法符号“!D()”,这就是DFinalize函数,这个“!D”函数被调用的时机是不确定的,要看GC什么时候决定回收该类占用的空间。

 

       ~D()析构函数和标准C++里的用法完全相同,释放以前获取的资源。而对!D()的用法则和Finalize函数一样,由于其调用时机是不确定的,所以千万不要依赖于它来释放关键资源(如文件句柄,Lock等)。

 

       ref类引入~D()!D()极大的方便了资源管理,也符合了标准C++程序员所熟悉的方式。Herb Sutter[3]把这个能力看成C++/CLIManaged环境下最为强大的能力之一。

ref   struct  D 

D()
{System::Console::WriteLine(“in D::D() ”);} 
~D(){System::Console::WriteLine(“in D::~D() ”);} 
!D(){System::Console::WriteLine(“Finalized! ”);} 
}

int  main() 

      D d;  
// in D::D() 
       ... 
}
  // d在这里析构!in D::~() 

你可能感兴趣的:(Visual,C++,.NET编程)