GC-proof resource classes
ponce via Digitalmars-d
digitalmars-d at puremagic.com
Sat Aug 29 06:14:23 PDT 2015
As a library writer I've struggled a bit with to provide easy
resource clean-up despite using class objects.
Is there reasons to use class objects that hold resources in the
first place vs Unique!T or RefCounted!T structs?
I think yes:
- classes have reference semantics without naming or runtime
overhead: you read Resource and not RefCounted!Resource
- no need to disable the postblit or have a valid .init is
another thing
That's about it from the top of my head and it may not be good
reasons!
As you probably know GC-called destructors enjoy a variety of
limitations:
- can't allocate,
- can't access members,
- aren't guaranteed to happen at all.
This makes GC-called destructors mostly useless for non-memory
resource release. IIRC Andrei suggested once that destructors
shouldn't be called at all by the GC, something that I agree with.
As such, some of us started providing a
release()/dispose()/close() method, and have the destructor call
it to support both scoped ownership and manual release.
But that introduce accidentally correct design when the
destructor is called by GC, and avoids a leak. This is arguably
worse than the initial problem.
It turns out separating calls of ~this() that are called by the
GC from those that are called for a deterministic reason is
enough, and support all cases I can think of:
Unique!T/scoped!T/.destroy/RefCounted!T/manual/GC-called
It works like this:
----------------
class MyResource
{
void* handle;
this()
{
handle = create_handle();
}
~this()
{
if (handle != null) // must support repeated calls for
the case (called by .destroy + called by GC later)
{
ensureNotInGC("MyResource");
free_handle(handle);
}
}
}
----------------
ensureNotInGC() is implemented like this:
----------------
void ensureNotInGC(string resourceName) nothrow
{
debug
{
import core.exception;
try
{
import core.memory;
void* p = GC.malloc(1); // not ideal since it
allocates
return;
}
catch(InvalidMemoryOperationError e)
{
import core.stdc.stdio;
fprintf(stderr, "Error: clean-up of %s incorrectly
depends on destructors called by the GC.\n", resourceName.ptr);
assert(false); // crash
}
}
}
--------------
Looks ugly? Yes, but it makes the GC acts as a cheap leak
detector, giving accurate messages for still opened resources.
More information about the Digitalmars-d
mailing list