Don't expect class destructors to be called at all by the GC

Mike Parker aldacron at gmail.com
Thu Dec 21 06:50:44 UTC 2017


On Thursday, 21 December 2017 at 04:10:56 UTC, user1234 wrote:
> On Thursday, 21 December 2017 at 02:57:00 UTC, Mike Franklin

>> Unfortunately, that doesn't really shed much light on this 
>> oddity.  So, specifically, under what circumstances are 
>> destructors not called?
>>

>
> When the GC is unaware of a class instance (an "unreferenced 
> object") it won't call its destructor. This happens when you 
> use alternative memory management strategy, for example using 
> Mallocator and make you get an unreferenced object that you 
> have to manage yourself.

The root of the problem is that in D, class destruction and 
finalization are conflated. It would be much more accurate to 
refer to ~this in classes as a finalizer. Then this sort of 
confusion wouldn't be so widespread.

Also, consider the current GC implementation finalizes any 
objects that haven't yet been finalized when it terminates. It 
terminates during runtime termination, but *after* static 
destructors are executed (which is how it should be). We already 
know that you can't rely on any GC memory references to be valid 
in a class destructor, but because of this cleanup phase, you 
also can't rely on any program state still being valid.

As an example, each of the Derelict packages used to 
(pointlessly) unload its shared library in a static destructor, 
but people repeatedly had segfaults at app exit because their 
class destructors were calling into the loaded libraries to 
release resources. The solution there was easy -- stop manually 
unloading the libraries and let the OS do it at process 
termination. But anyone who wants to unload any resources in a 
class destructor needs to be aware of this issue in case a static 
destructor somewhere is getting to it first. And I still see code 
using Derelict where people unload the library themselves in a 
static destructor.

I just don't even bother with class destructors. Without a 
guarantee that they can run and without any sort of deterministic 
behavior, it's really not appropriate to refer to them as 
destructors and they're about as useful as Java finalizers, which 
means not at all. In order to make them less error prone, we need 
to separate the concept of destruction from finalization and 
allow both destructors and finalizers. That's what I've taken to 
doing manually, by implementing a `terminate` function in my 
classes that I either call directly or via a ref-counted 
templated struct called Terminator.


More information about the Digitalmars-d-learn mailing list