为什么C#中的Dispose模式在C ++中不像RAII那样工作

时间:2013-08-29 14:38:12

标签: c# c++ .net raii

所以我只是阅读非垃圾收集语言的RAII模式,这个section引起了我的注意:

  

开发自定义类时通常会遇到此限制。 C#和Java中的自定义类必须显式实现dispose方法,以便与客户端代码进行处置兼容。 dispose方法必须包含显式关闭属于该类的所有子资源。在使用RAII的C ++中不存在这种限制,其中自定义类的析构函数会自动地递归地破坏所有子资源,而不需要任何显式代码。

为什么C ++可以正确跟踪在RAII模式中分配的这些资源,但是我们没有使用C#using构造获得这个可爱的Stack Unwinding?

4 个答案:

答案 0 :(得分:10)

假设一个对象O由两个拥有资源的对象R和S组成。如果O被销毁会怎样?

在使用RAII的C ++中,对象可以拥有其他对象,这样一个对象的破坏必然会耦合到另一个对象。如果O 拥有 R和S - 通过按值存储它们,或者拥有依次拥有R和S的东西(unique_ptr,按值存储R和S的容器),那么O的破坏必然破坏R和S.只要R和S的析构函数在它们之后正确清理,O就不需要手动执行任何操作。

相比之下,C#对象没有所有者决定其生命周期何时结束。即使O被确定性地销毁(通常不是),R和S也可以通过另一个参考来达到。更重要的是,O引用R和S的方式与任何其他局部变量,对象字段,数组元素等引用R和S的方式相同。换句话说,没有办法表明所有权,因此计算机可以' t决定什么时候假定被销毁,以及什么时候它只是一个非拥有/借用的参考。当然你不希望这段代码关闭文件吗?

File f = GetAFile();
return f; // end of method, the reference f disappears

但就CLR而言,此处来自本地f的引用与从O到R / S的引用完全相同。

TL; DR 所有权。

答案 1 :(得分:3)

在C ++中,对象具有明确的生命周期。自动变量的生命周期在超出范围时结束,动态分配的对象的生命周期在被删除时结束。

在C#中,大多数对象是动态分配的,没有删除。因此,对象在被“删除”时没有定义的时间点。那么,你最接近的是using

答案 2 :(得分:2)

简短的回答:您最后一次在Java / C#析构函数中清理自己的内存分配/资源分配是什么时候?事实上,你最后一次记得编写Java / C#析构函数是什么时候?

由于您负责在C ++中自行清理,因此您必须进行清理。因此,当您停止使用资源时(如果您已经编写了高质量的代码),它将立即被清除。

在托管语言中,垃圾收集器负责进行清理。在您停止使用它之后很长时间内,您的分配资源仍然存在(如果垃圾收集器实现不佳)。当托管对象创建非托管资源(例如数据库连接)时,这是一个问题。这就是存在Dispose方法的原因 - 告诉那些非托管资源消失。由于析构函数在垃圾收集器清理内存之前不会被调用,因此进行清理仍然会使(有限)资源保持打开的时间超过其所需的时间。

答案 3 :(得分:2)

因为要在C#中正确实现它,你需要以某种方式标记类拥有哪些对象以及共享哪些对象。也许是与此类似的语法:

// NOT VALID CODE
public class Manager: IDisposable
{
     // Tell the runtime to call resource.Dispose when disposing Manager
     using private UnmanagedResource resource;
}

我的猜测是他们决定不去那条路,因为如果你必须标记你拥有的对象,你必须编写有关它的代码。如果你必须编写有关它的代码,你可以在Dispose方法中编写它,为你拥有的对象调用Dispose:)

在C ++中,对象所有权通常非常明确 - 如果您拥有实例本身,则拥有它。在C#中,你永远不会持有实例,你总是持有一个引用,引用可以是你拥有的东西或你使用的东西 - 没有办法分辨它是哪一个对于特定实例,则为true。