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