class destructors must be @disabled?

Ali Çehreli acehreli at yahoo.com
Wed May 18 18:02:01 UTC 2022


I am writing these in view of class objects commonly being constructed 
as GC-owned objects.

GUIDELINES:

We've seen the following points mentioned in these forums.

- (This one is just a complication.) Classes don't have destructors 
anyway; they should be called finalizers.

- Don't allocate memory from the GC in a class destructor. (For example, 
writeln may be fine with simple types but writefln or format would not be.)

- Don't touch any GC-owned member in a class destructor because that 
member may have already been finalized.

SOME FACTS:

- Class destructors are not guarenteed to be executed. And this cannot 
be controlled by the programmer. For example, the user of the program 
can pass the following command line option and the GC will not execute 
class destructors:

   $ myprogram "--DRT-gcopt=cleanup:none" ...

(The following example will run fine when started that way.)

- Even if the destructor is executed by the GC, when it is executed 
exactly is naturally unspecified ("sometime in the future"). For that 
reason, it does not make sense to leave important responsibilities to 
class destructors like closing a File. Not only the file may not be 
closed, you may be exceeding resources that the OS provides by holding 
on to them for too long.

HORROR:

Now, a big one that I've just realized.

- The two guidelines above (the "don't touch class members" one and the 
"don't allocate" one) miss an important fact that they apply recursively 
even to struct members of classes. For example, structs that are used as 
members of a class cannot allocate memory in their destructors either.

The following program ends with core.exception.InvalidMemoryOperationError:

import std.format;

void closeConnection(string s) {
}

struct S {
   int id;

   ~this() {
     closeConnection(format!"%s signing off"(id));
   }
}

class C {
   S s;

   this(int id) {
     s = S(id);
   }

   // @disable ~this();
}

void main() {
   // Just 1 is sufficient to show the error in Ali's environment.
   enum N = 1;
   foreach (i; 0 .. N) {
     auto c = new C(i);
   }
}

Note how the struct is written in good faith and the class is obeying 
all the guidelines (by not even defining a destructor). I think the 
problem is the missed guideline that is on the subject line: classes 
should @disable their destructors. The program above will work fine when 
that line is uncommented.

Of course, we still should and do have the power to shape our programs 
any way we want but I think '@disable ~this();' should be added to 
classes as a general rule unless the programmer knows it will work 
otherwise.

What do you think?

Ali


More information about the Digitalmars-d-learn mailing list