非确定性资源管理是抽象的泄漏吗?


9

据我所知,资源管理有两种普遍形式:确定性破坏和显式破坏。前者的示例是C ++析构函数和智能指针或Perl的DESTROY子例程,而后者的示例将是Ruby的块至管理资源范例或.NET的IDispose接口。

较新的语言似乎选择后者,这可能是使用非引用计数类型的垃圾收集系统的副作用。

我的问题是:鉴于智能指针或引用计数垃圾收集系统的析构函数(几乎是同一件事)允许隐式和透明的资源销毁,它比依赖于显式的非确定性类型的抽象性泄漏少吗?符号?

我将举一个具体的例子。如果您有一个超类的三个C ++子类,则其中一个实现可能不需要任何特定的销毁。也许它以另一种方式发挥了魔力。它不需要任何特殊的销毁是无关紧要的-所有子类仍然以相同的方式使用。

另一个示例使用Ruby块。两个子类需要释放资源,因此,即使其他特定子类可能不需要它,因为它们不需要特殊的销毁,所以超类会选择在构造函数中使用块的接口。

后者是否泄漏了资源破坏的实施细节,而前者却没有?

编辑:比方说,将Ruby与Perl进行比较可能更公平,因为其中一个具有确定性的销毁能力,而另一个则没有,但是它们都被垃圾回收了。


5
我很想说“是”,但是我想听听其他人对此说些什么。
Bart van Ingen Schenau 2013年

透明的资源破坏?除了必须使用智能指针而不是普通指针这一事实之外?我不认为这比仅具有一种访问对象的机制(引用)(在C ++中,您至少有四个或五个)更透明。
Giorgio 2013年

@乔治:“访问对象的方式”非常模糊。你是说读还是写?const /易碎的资格?指针并不是真正的“访问对象的方法”。几乎所有表达式都会产生对象,而取消引用指针并不是那么特殊。
MSalters

1
@Giorgio:从OOP的意义上讲,您不能将消息发送到C ++指针。您需要取消引用指针以发送消息(*ptr).Message()或进行等效操作ptr->Message()。存在无限数量的允许表达式,这((*ptr))->Message也是等效的。但他们全都归结为expressionIdentifyingAnObject.Message()
MSalters 2013年

1
进行口算时,您需要避免回圈。因此,抽象也以不同的方式泄漏。
CodesInChaos

Answers:


2

您自己的示例回答了这个问题。透明破坏显然比显性破坏少泄漏。它可以泄漏,但泄漏较少。

显式销毁类似于C中具有所有陷阱的malloc / free。也许用一些语法糖使它显得基于作用域。

透明销毁相对于显式销毁的一些好处:-
相同的使用模式
-您不能忘记释放资源。
-清理细节不会在使用时乱扔垃圾。


2

实际上,抽象的失败不是垃圾收集不是确定性的事实,而是对象对它们所引用的事物“感兴趣”,而对它们不对之事物不感兴趣的想法参考。若要了解原因,请考虑一个对象的场景,该对象保持与绘制特定控件的频率相对应的计数器。在创建时,它订阅控件的“绘画”事件,并在处理后取消订阅。click事件只是增加一个字段,然后一个方法getTotalClicks()返回该字段的值。

创建计数器对象时,它必须使对其自身的引用存储在其监视的控件中。该控件确实不在乎计数器对象,并且如果计数器对象及其引用不再存在就将感到高兴,但是只要引用确实存在,它将每次都调用该对象的事件处理程序它画自己。这个动作对控件完全没有用,但是对任何会调用getTotalClicks()该对象的人都是有用的。

如果例如一种方法是创建一个新的“绘画计数器”对象,对该控件执行一些操作,观察该控件被重新绘制了多少次,然后放弃了绘画计数器对象,则该对象甚至仍将订阅该事件尽管没有人会关心该对象及其引用完全消失。但是,直到控件本身成为对象之前,这些对象才有资格进行收集。如果该方法将在控件的生存期内被调用数千次(可能的情况),则可能会导致内存溢出,但事实上,N次调用的代价可能是O(N ^ 2)或O (N ^ 3),除非订阅处理非常有效并且大多数操作实际上不涉及任何绘画。

可以通过让控件保持对计数器对象的弱引用而不是强对象来处理这种特殊情况。弱订阅模型很有用,但在一般情况下不起作用。假设不是希望拥有一个对象来监视单个控件中的单一事件,而是希望拥有一个事件记录器对象来监视多个控件,并且系统的事件处理机制是每个控件都需要引用到另一个事件记录器对象。在那种情况下,将控件链接到事件记录器的对象仅在两个对象都保持活动状态被监视的控件和事件记录器仍然有用。如果控件和事件记录器都没有对链接事件有很强的引用,即使它仍然“有用”,它也将不复存在。如果其中任何一个发生了重大事件,则即使另一个死亡,链接对象的生存期也可能无用地延长。

如果在宇宙中的任何地方都不存在对任何对象的引用,则可以安全地将该对象视为无用的,并将其从存在中删除。但是,存在对对象的引用这一事实并不意味着该对象是“有用的”。在许多情况下,对象的实际用途将取决于对其他对象的引用的存在(从GC角度来看)与它们完全无关。

如果在没有人对对象感兴趣时确定地通知对象,则它们将能够使用该信息来确保将从该知识中受益的任何人都被告知。然而,在没有这种通知的情况下,如果仅知道存在的一组引用而不知道那些引用附带的语义,就没有通用的方法来确定哪些对象被认为是“有用的”。因此,即使GC可以立即检测到对象放弃,任何假定引用的存在或不存在足以进行自动化资源管理的模型都将注定失败。


0

不,“析构函数”或其他表示“必须销毁该类的接口”是该接口的契约。如果您创建不需要特殊销毁的子类型,则我倾向于考虑违反Liskov替代原理。

至于C ++与其他,没有太大的区别。C ++强制在所有对象上使用该接口。语言需要它们时,抽象不会泄漏。


4
“如果您创建不需要特殊销毁的子类型”这不是LSP违规,因为no-op是销毁的有效特殊情况。问题是将破坏要求添加到派生类时。
CodesInChaos

我在这里很困惑。如果需要在C ++子类中添加特殊的销毁代码,则它根本不会更改其使用模式,因为它是自动的。这意味着父类和子类仍然可以互换使用。但是使用显式的资源管理符号,需要显式销毁的子类将使其用法与超类不兼容,不是吗?(假设超类不需要明确销毁。)
路易·杰克曼

@CodesInChaos-是的,我想是真的。
Telastyn

@ljackman:需要特殊销毁的类对调用其构造函数以确保其实现的人造成了负担。这不会造成LSP冲突,因为a DerivedFooThatRequiresSpecialDestruction只能由调用的代码来创建new DerivedFooThatRequiresSpecialDestruction()。另一方面,如果工厂方法返回一个DerivedFooThatRequiresSpecialDestruction不希望破坏的代码,则会违反LSP。
supercat 2014年

0

我的问题是:鉴于智能指针或引用计数垃圾收集系统的析构函数(几乎是同一件事)允许隐式和透明的资源销毁,它比依赖于显式的非确定性类型的抽象性泄漏少吗?符号?

不必手动监视周期既不是隐含的也不是透明的。唯一的例外是参考计数系统的语言在设计上禁止循环。Erlang可能是此类系统的一个示例。

因此,这两种方法都会泄漏。主要区别在于析构函数在C ++中到处都有泄漏,但IDispose在.NET上很少见。


1
除了循环,这种情况极少发生,几乎没有发生,除非在明确循环的数据结构中。主要区别在于C ++中的析构函数可在任何地方正确处理,但IDispose很少处理.NET中的问题。
DeadMG

“周期极少发生”。用现代语言?我会挑战这个假设。
乔恩·哈罗普
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.