Order of destruction when garbage collection kicks in

Regan Heath regan at netmail.co.nz
Wed Apr 10 03:08:22 PDT 2013


On Wed, 10 Apr 2013 10:48:30 +0100, Henning Pohl <henning at still-hidden.de>  
wrote:

> On Tuesday, 9 April 2013 at 20:46:12 UTC, Steven Schveighoffer wrote:
>> No.  You are not allowed to access any GC-managed resources inside a  
>> destructor.
> Okay, I finally found it: http://dlang.org/class.html#destructors. But  
> it's not listed here: http://dlang.org/garbage.html.
>> And it won't be null, because that would be extremely costly.  Consider  
>> if 300 objects had a pointer to a block of memory.  Now those 300  
>> objects all go away.  By your expectation, if the targeted block of  
>> memory was collected, the GC would have to spend time going through all  
>> those 300 pointers, setting them to null.  When they are about to all  
>> be destroyed.  99.99% of the time, that would be wasted, as those 300  
>> objects may not even have destructors that care.
> I thought about the case when the user sets the reference to null before  
> the destructor was even called.
>> A destructor is ONLY for destroying non-GC managed resources, nothing  
>> else.  Like an OS file handle for instance.
> Imagine this case:
> ...
> Any instance of B always needs to be destructed _before_ the A it's  
> connected to. How do you express this in D?

// External C library.
extern(C) {
     alias void* a_handle;
     alias void* b_handle;

     a_handle create_a();
     void destroy_a(a_handle);

     b_handle create_b(a_handle);
     void destroy_b(b_handle);
}

class A {
     this() {
         // An a_handle owns multiple b_handles.
         handle = create_a();
     }

     ~this() {
	    Dispose(false);		// ** new **
     }

     private a_handle handle;
	
	// ** new **
	private bool _disposed;	
	
	public void Dispose()
	{
	  Dispose(true);
	}
	
	private void Dispose(bool disposing)
	{
	  if (!_disposed)
	  {
	    _disposed = true;
	    if (disposing)
		{
           // Destroys all bs connected with this a.
           destroy_a(handle);
		}
	  }
	}
}

class B {
     this(A a) {
         this.a = a;
         // Creates a b_handle connected to the given a_handle.
         handle = create_b(a.handle);
     }

     ~this() {
         Dispose(false); // **new **
     }

     private A a;
     private b_handle handle;
	
	// ** new **
	private bool _disposed;	
	
	public void Dispose()
	{
	  Dispose(true);
	}
	
	private void Dispose(bool disposing)
	{
	  if (!_disposed)
	  {
	    _disposed = true;
	    if (disposing)
		{
           destroy_b(handle);
		  a.Dispose(true);  // or maybe not depending on how many b handles  
there are?
		}
	  }
	}
}

Yes, your code will leak library handles/resources if you forget to  
manually call Dispose() on (at least) the B above (assuming it calls  
Dispose on its A).  It's a pain and not perfect, but I don't think it can  
be - in D - without compiler/GC support for a disposal pattern like this  
(as C# has).

The disposal pattern in C# is not 'free' in terms of costs to the running  
of the application though, it causes objects to survive a GC collection,  
any they in turn keep other objects alive so there is a very real issue of  
objects living longer and resource use being higher.  Add to that, that  
objects can be resurrected after the first collection, before the GC  
finalizer calls Dispose and you have all sorts of tricky edge cases and  
timing windows.

So, it's not without costs.  But, in D, you can do the above and ensure  
you call Dispose manually and at worst it's the same as manual memory  
management in C/C++ for a smaller sub-set of your code making it more  
manageable, but perhaps easier to forget and get wrong.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/


More information about the Digitalmars-d mailing list