From what I can see, there are two pervasive forms of resource-management: deterministic destruction and explicit. Examples of the former would be C++ destructors and smart pointers or Perl’s DESTROY sub, whilst an example of the latter would be Ruby’s blocks-to-manage-resources paradigm or .NET’s IDispose interface.
Newer languages seem to opt for the latter, perhaps as a side-effect of using garbage collection systems of the non-reference-counting variety.
My question is this: given that destructors for smart pointers or reference-counting garbage collection systems — almost being the same thing — allow implicit and transparent resource destruction, is it a less leaky abstraction than the non-deterministic types which rely on explicit notation?
I’ll give a concrete example. If you have three C++ subclasses of a single superclass, one may have an implementation that doesn’t need any specific destruction. Perhaps its does its magic in another way. The fact that it doesn’t need any special destruction is irrelevant — all of the subclasses are still used in the same way.
Another example uses Ruby blocks. Two subclasses need to free resources, so the superclass opts for an interface that uses a block in the constructor, even though other specific subclasses might not need it since they require no special destruction.
Is it the case that the latter leaks implementation details of the resource destruction, whilst the former does not?
EDIT: Comparing, let’s say, Ruby to Perl might be more fair since one has deterministic destruction and the other hasn’t, yet they’re both garbage-collected.
9
Your own example answers the question. The transparent destruction is clearly less leaky than explicit destruction. It can leak, but is less leaky.
Explicit destruction is analogous to malloc/free in C with all the pitfalls. Maybe with some syntactic sugar to make it appear scope-based.
Some of the benefits of transparent destruction over explicit:
–same usage pattern
–you can’t forget to release the resource.
–clean up details do not litter the landscape at the point of usage.
The failure in the abstraction is actually not the fact that garbage-collection is non-deterministic, but rather in the idea that objects are “interested” in things that they hold references to, and are not interested in things to which they do not hold references. To see why, consider the scenario of an object which maintains a counter of how often a particular control is painted. On creation, it subscribes to the control’s “paint” event, and on disposal it unsubscribes. The click event simply increments a field, and a method getTotalClicks()
returns the value of that field.
When the counter object is created, it must cause a reference to itself to be stored within the control it’s monitoring. The control really doesn’t care about the counter object, and would be just as happy if the counter object, and the reference to it, ceased to exist, but as long as the reference does exist it will call that object’s event handler every time it paints itself. This action is totally useless to the control, but would be useful to anyone who would ever call getTotalClicks()
on the object.
If e.g. a method were to create a new “paint-counter” object, perform some action on the control, observe how many times the control was repainted, and then abandon the paint-counter object, the object would remain subscribed to the event even though nobody would ever care if the object and all references to it simply vanished. The objects would not become eligible for collection, however, until the control itself is. If the method were one that would be invoked many thousands of times within the control’s lifetime [a plausible scenario], it could cause a memory overflow but for the fact that the cost of N invocations would likely be O(N^2) or O(N^3) unless subscription-processing was very efficient and most operations didn’t actually involve any painting.
This particular scenario could be handled by giving having the control keep a weak reference to the counter object rather than a strong one. A weak-subscription model is helpful, but doesn’t work in the general case. Suppose that instead of wanting to have an object which monitors a single kind of event from a single control, one wanted to have an event-logger object which monitored several controls, and the system’s event-handling mechanism was such that each control needed a reference to a different event-logger object. In that case, the object linking a control to the event logger should remain alive only as long as both the control being monitored and the event logger remain useful. If neither the control nor the event logger holds a strong reference to the linking event, it will cease to exist even though it’s still “useful”. If either holds a strong event, the lifetime of the linking object may be uselessly extended even if the other one dies.
If no reference to an object exists anywhere in the universe, the object may safely be considered useless and eliminated from existence. The fact that a reference exists to an object, however, does not imply that the object is “useful”. In many cases, the actual usefulness of objects will depend upon the existence of references to other objects which–from the GC perspective–are totally unrelated to them.
If objects are deterministically notified when nobody is interested in them, they will be able to use that information to make sure that anyone who would benefit from that knowledge is informed. In the absence of such notification, however, there is no general way to determine what objects are considered “useful” if one knows only the set of references which exist, and not the semantic meaning attached to those references. Thus, any model which assumes that the existence or non-existence of references is sufficient for automated resource management would be doomed even if the GC could instantly detect object abandonment.
No” the destructor, or other interface that says “this class must be destroyed” is a contract of that interface. If you make a subtype that doesn’t require special destruction I would be inclined to consider that a violation of the Liskov Substitution Principle.
As for C++ vs. others, there’s not much difference. C++ forces that interface on all it’s objects. Abstractions can’t leak when they’re required by the language.
4
My question is this: given that destructors for smart pointers or reference-counting garbage collection systems — almost being the same thing — allow implicit and transparent resource destruction, is it a less leaky abstraction than the non-deterministic types which rely on explicit notation?
Having to watch for cycles by hand is neither implicit nor transparent. The only exception is a reference counting system with a language that prohibits cycles by design. Erlang might be an example of such a system.
So both approaches leak. The main difference is that destructors leak everywhere in C++ but IDispose
is very rare on .NET.
2