strange work of GC

FG via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Feb 8 03:03:40 PST 2015


On 2015-02-08 at 06:36, Mike Parker wrote:
> On 2/8/2015 11:32 AM, FG wrote:
>> On 2015-02-08 at 01:20, Mike Parker wrote:
>>> In your case, forget destructors and the destroy method. Just
>>> implement a common method on all of your objects that need cleanup
>>> (perhaps name it 'terminate') and call that. This gives you the
>>> deterministic destruction that you want (the same as calling destroy
>>> on each object) while avoiding the possibility that the GC can call
>>> your cleanup method.
>>
>> What is wrong with doing all that in a destructor? I don't know if it is
>> just an implementation detail, but a destroyed object is either
>> zero-filled or reinitialized to the default, so, if implemented
>> correctly, it knows whether a cleanup is required (and I'm assuming that
>> a model of simple single ownership is used, like in Qt). Therefore it
>> should be safe to use destroy to finalize an object and even to put
>> destroy in its destructor in order to perform a controlled cascade
>> destruction of all the children that require immediate cleanup (ie.
>> releasing expensive non-GC resources, closing connections, etc.). The
>> main difference with C++ being that you would only force finalization,
>> but then let the GC free the memory in its normal fashion.
>>
>>
>>
> First, there are no guarantees about when or if a destructor is going to be called. The fact that the current GC calls the destructors of all live objects at application shutdown is an implementation detail. If you want deterministic destruction, you can not rely on destructors.

I admit that I became accustomed to that implementation detail and would like it to stay.

> Second, if you are going to call destroy on every object to clean them up, then in principle that's fine. But now you have to be careful that no mistakes slip into the code base, e.g. forgetting to call destroy on an object that touches GC memory in its destructor.

One could also forget to call terminate(). :)

The advantage of ~this() is that it is standard, while terminate() is not. You call it that, someone else might call it finalize(), close() or whatever. I do see a benefit in using terminate() directly instead of the GC-fired destructor, because it's not constrained by having to steer clear of the InvalidMemoryOperationError, but it is overshadowed by inconvenience. So let's assume that terminate() is GC-friendly (ie. would work even when called from a destructor).

> By separating resource cleanup from object destruction, you get both deterministic cleanup and more freedom in choosing whether or not to clean up at all.

I separate them only when the object needs it (flush a write, close a connection, release a big chunk of memory, etc.), by calling destroy and not waiting until the GC decides that it's time to deallocate memory for that object and performs the destruction by itself. For every other case of object I also do not care when and if it gets cleaned up.

>At app exit, I only call terminate on objects that absolutely need to do something before the process exits, like writing a final message to a log file [...] resource cleanup only happens when and if I say.

Exactly, but if I put a call to terminate() in the destructor, then clean up will be automated in those cases when I don't care about cleaning up myself or *forget* to do it.

>That just isn't possible if all cleanup is in destructors. You either have to destroy *every* object yourself, or be vigilant about which objects you let the GC call destructors on.

Why not? Are you saying that because of the lack of guarantees that a destructor will be called by the GC? And not *every* object -- it was stated that we don't care about all those objects that don't require special clean up.



More information about the Digitalmars-d-learn mailing list