Class destructors - clarify what is safe

H. S. Teoh hsteoh at qfbox.info
Sun Feb 15 01:36:55 UTC 2026


On Sun, Feb 15, 2026 at 12:58:14AM +0000, Forum User via Digitalmars-d-learn wrote:
[...]
> I thought that also in D all the members and the base objects of the
> object which is destructed are still valid and accessible until the
> destructor finished.

Non-GC-allocated members are still valid until the dtor is done.

But any class references and references to GC-allocated objects are not
guaranteed to be valid, because the order in which the GC calls class
dtors is not guaranteed.

Basically, the GC finds the set of dead objects in an unspecified order,
which from the POV of user code should be regarded as random and
unpredictable.  Consider an object c of class C:

```
	class C {
		A a;
		B b;
		this(A _a, B _b) { a = _a; b = _b; }
		~this() {
		}
	}

	auto a = new A;
	auto b = new B;
	auto c = new C(a, b);
```

When c goes out of scope, a and b are still referenced by c, but are no
longer reachable from the rest of the program.  The order in which the
GC finds dead objects is not specified and up to the specifics of its
implementation, so it could very well be that it first discovers that a
is no longer reachable, and then discovers that c is unreachable.
Depending on the implementation, it may call a's dtor first, free it,
and then call c's dtor, then afterwards discover that c was holding the
last reference to b, so b should also be collected, and therefore b's
dtor is called last.

This means that when c's dtor is run, a is no longer a valid reference
(its dtor has run and its memory has be reclaimed by the GC, and
potentially used for a new, unrelated object).  So it's unsafe for c's
dtor to access a.

Of course, b is still valid because the GC hasn't discovered that it's
dead (yet).  But that doesn't mean that it will always happen this way.
Depending on implementation details like the GC algorithm, the specific
memory addresses of objects, etc., it could be that in a different GC
collection cycle, b gets collected before c.  So it's also unsafe to
reference b from c's dtor -- it might coincidentally work this time
round, but next time it may cause the program to crash.

//

In short, inside a class dtor you should NOT assume that any reference
to a GC-allocated object is still valid.  There is NO guarantee that
referenced objects get collected before/after the referencing object. It
can happen in any arbitrary order.  (Any such guarantee wouldn't make
sense in the context of a GC anyway, because it'd prevent the GC from
collecting cycles properly. If A references B and B also references A,
then which dtor should be called first?)

This essentially limits the usefulness of dtors to only cleaning up
external resources not managed by the GC.  For the cleanup of all
GC-allocated objects, user code should not try to (and does not need to)
clean up manually. That's the GC's job.

GC collection can reclaim objects in any arbitrary order, and user code
should not rely on any specific order.  If you need to control the order
of collection, you should not use the GC; you should use C's malloc() or
some other memory management mechanism.


T

-- 
It only takes one twig to burn down a forest.


More information about the Digitalmars-d-learn mailing list