10

所以我只是在阅读关于非垃圾收集语言的RAII模式,这部分引起了我的注意:

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

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

4

4 回答 4

12

假设一个对象 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这里从local的引用和从O到R/S的引用是完全一样的。

TL;DR所有权。

于 2013-08-29T14:58:08.167 回答
3

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

在 C# 中,大多数对象都是动态分配的,并且没有删除。因此,对象在“删除”时没有定义的时间点。那么,您拥有的最接近的东西是using.

于 2013-08-29T14:43:05.720 回答
2

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

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

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

于 2013-08-29T14:44:53.260 回答
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# 中,您永远不会持有实例本身,您始终持有引用,并且引用可以是您拥有的东西或您使用的东西- 没有办法告诉特定实例哪个是正确的。

于 2013-08-29T14:52:36.677 回答