www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Should destructors be able to tell who called them?

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
One of the common problems of destructors is that they cannot assume any  
of their GC-allocated resources are valid when inside the destrutor.

The spec says:

The garbage collector is not guaranteed to run the destructor for all  
unreferenced objects. Furthermore, the order in which the garbage  
collector calls destructors for unreference objects is not specified. This  
means that when the garbage collector calls a destructor for an object of  
a class that has members that are references to garbage collected objects,  
those references may no longer be valid. This means that destructors  
cannot reference sub objects. This rule does not apply to auto objects or  
objects deleted with the DeleteExpression, as the destructor is not being  
run by the garbage collector, meaning all references are valid.

Let's analyze that last sentence (BTW, I think where it says auto, it  
should say scope).  "This does not apply to objects deleted with the  
DeleteExpression..."  Well, how the hell do you know while in the  
destructor whether you were called via delete/clear or from the GC?  You  
don't.  So the destructor *must* be written as if you are called from the  
GC, because the GC may be calling it, it is up to the user to determine  
whether your class will be destroyed via delete/clear or the GC.  So you  
must assume worst case.  Unless you declare your class as scope, which I  
don't even know if anyone does that anymore (I think it's scheduled for  
deprecation anyways).

But, what if the destructor was given an idea of whether its resources are  
valid or not?  Then you could do something different based on that input.

For example, an object that wants to open a file can do so, and in the  
destructor, use the determination of validity to decide whether to close  
the file or not.

This can be implemented by an optional parameter to the destructor that's  
always passed, but it's not necessary to use it.  i.e. you could declare  
your destructor like this:

~this(bool deterministic)
{
    if(deterministic) file.close();
}

or like this:

~this()
{
}

Scope classes (if allowed to exist) or calls to clear will set  
deterministic to true.  The GC will set it to false.

This would make destructors a lot more useful.  Thoughts?

-Steve
Aug 10 2010
next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-08-10 14:25:48 -0400, "Steven Schveighoffer" 
<schveiguy yahoo.com> said:

 ~this(bool deterministic)
 {
     if(deterministic) file.close();
 }
 
 or like this:
 
 ~this()
 {
 }
 
 Scope classes (if allowed to exist) or calls to clear will set  
 deterministic to true.  The GC will set it to false.
 
 This would make destructors a lot more useful.  Thoughts?
In Objective-C we have the dealloc method (deterministic) and the finalize method (non-deterministic). The former is used in a reference-counted environment while the second is used when memory is managed by a GC. One issue with having one destructor with a deterministic argument (as opposed to having two separate destructors) is that non-deterministic destructors should probably not be allowed in SafeD because you can then get access to dangling pointers and you can even pass them around to make them live long after the collection has terminated. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Aug 10 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 14:38:13 -0400, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2010-08-10 14:25:48 -0400, "Steven Schveighoffer"  
 <schveiguy yahoo.com> said:

 ~this(bool deterministic)
 {
     if(deterministic) file.close();
 }
  or like this:
  ~this()
 {
 }
  Scope classes (if allowed to exist) or calls to clear will set   
 deterministic to true.  The GC will set it to false.
  This would make destructors a lot more useful.  Thoughts?
In Objective-C we have the dealloc method (deterministic) and the finalize method (non-deterministic). The former is used in a reference-counted environment while the second is used when memory is managed by a GC. One issue with having one destructor with a deterministic argument (as opposed to having two separate destructors) is that non-deterministic destructors should probably not be allowed in SafeD because you can then get access to dangling pointers and you can even pass them around to make them live long after the collection has terminated.
Good point. I'd restate it as should safeD be able to define deterministic destructors? i.e. a solution to this is not to allow defining class destructors at all in safeD. That might be too limiting, but not any more limiting than it is now... I don't know what the right answer is. What I was looking for was a solution that was backwards compatible with current code, and one that didn't require you to call __dtor directly. -Steve
Aug 10 2010
prev sibling next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
Steven Schveighoffer Wrote:

 One of the common problems of destructors is that they cannot assume any  
 of their GC-allocated resources are valid when inside the destrutor.
Just a note. It's already possible to do this in D2 using an interface. Here's a thread about it: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=89443&header And some sample code: import core.runtime; interface Disposable { void dispose(); } bool handler( Object o ) { auto d = cast(Disposable) o; if( d !is null ) { d.dispose(); return false; } return true; } static this() { Runtime.collectHandler = &handler; }
Aug 10 2010
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 14:51:56 -0400, Sean Kelly <sean invisibleduck.org>  
wrote:

 Steven Schveighoffer Wrote:

 One of the common problems of destructors is that they cannot assume any
 of their GC-allocated resources are valid when inside the destrutor.
Just a note. It's already possible to do this in D2 using an interface. Here's a thread about it: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=89443&header And some sample code: import core.runtime; interface Disposable { void dispose(); } bool handler( Object o ) { auto d = cast(Disposable) o; if( d !is null ) { d.dispose(); return false; } return true; } static this() { Runtime.collectHandler = &handler; }
I don't like the semantics. If you inherit from Disposable, then dispose is the nondeterministic, and ~this() becomes deterministic? How does that work if you inherit from a non-Disposable object? I guess it doesn't hurt, but I like my solution better for two other reasons: 1. you are not doing a dynamic cast (i.e. linear search) on every collected object 2. it costs next to nothing to put a bool on the stack. -Steve
Aug 10 2010
prev sibling parent reply "Yao G." <nospamyao gmail.com> writes:
Thanks for this Sean. Although I concur with Steve that it looks weird, at  
least is usable.

Yao G.


On Tue, 10 Aug 2010 13:51:56 -0500, Sean Kelly <sean invisibleduck.org>  
wrote:

 Steven Schveighoffer Wrote:

 One of the common problems of destructors is that they cannot assume any
 of their GC-allocated resources are valid when inside the destrutor.
Just a note. It's already possible to do this in D2 using an interface. Here's a thread about it: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=89443&header And some sample code: import core.runtime; interface Disposable { void dispose(); } bool handler( Object o ) { auto d = cast(Disposable) o; if( d !is null ) { d.dispose(); return false; } return true; } static this() { Runtime.collectHandler = &handler; }
-- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Aug 10 2010
parent Sean Kelly <sean invisibleduck.org> writes:
That hook was really added for other purposes anyway.  I mostly wanted to note
that it's possible to try this out now without any language changes.

Yao G. Wrote:

 Thanks for this Sean. Although I concur with Steve that it looks weird, at  
 least is usable.
 
 Yao G.
 
 
 On Tue, 10 Aug 2010 13:51:56 -0500, Sean Kelly <sean invisibleduck.org>  
 wrote:
 
 Steven Schveighoffer Wrote:

 One of the common problems of destructors is that they cannot assume any
 of their GC-allocated resources are valid when inside the destrutor.
Just a note. It's already possible to do this in D2 using an interface. Here's a thread about it: http://www.digitalmars.com/pnews/read.php?server=news.digitalmars.com&group=digitalmars.D&artnum=89443&header And some sample code: import core.runtime; interface Disposable { void dispose(); } bool handler( Object o ) { auto d = cast(Disposable) o; if( d !is null ) { d.dispose(); return false; } return true; } static this() { Runtime.collectHandler = &handler; }
-- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Aug 10 2010
prev sibling next sibling parent reply Mafi <mafi example.org> writes:
Am 10.08.2010 20:25, schrieb Steven Schveighoffer:
 One of the common problems of destructors is that they cannot assume any
 of their GC-allocated resources are valid when inside the destrutor.

 The spec says:

 The garbage collector is not guaranteed to run the destructor for all
 unreferenced objects. Furthermore, the order in which the garbage
 collector calls destructors for unreference objects is not specified.
 This means that when the garbage collector calls a destructor for an
 object of a class that has members that are references to garbage
 collected objects, those references may no longer be valid. This means
 that destructors cannot reference sub objects. This rule does not apply
 to auto objects or objects deleted with the DeleteExpression, as the
 destructor is not being run by the garbage collector, meaning all
 references are valid.

 Let's analyze that last sentence (BTW, I think where it says auto, it
 should say scope). "This does not apply to objects deleted with the
 DeleteExpression..." Well, how the hell do you know while in the
 destructor whether you were called via delete/clear or from the GC? You
 don't. So the destructor *must* be written as if you are called from the
 GC, because the GC may be calling it, it is up to the user to determine
 whether your class will be destroyed via delete/clear or the GC. So you
 must assume worst case. Unless you declare your class as scope, which I
 don't even know if anyone does that anymore (I think it's scheduled for
 deprecation anyways).

 But, what if the destructor was given an idea of whether its resources
 are valid or not? Then you could do something different based on that
 input.

 For example, an object that wants to open a file can do so, and in the
 destructor, use the determination of validity to decide whether to close
 the file or not.

 This can be implemented by an optional parameter to the destructor
 that's always passed, but it's not necessary to use it. i.e. you could
 declare your destructor like this:

 ~this(bool deterministic)
 {
 if(deterministic) file.close();
 }

 or like this:

 ~this()
 {
 }

 Scope classes (if allowed to exist) or calls to clear will set
 deterministic to true. The GC will set it to false.

 This would make destructors a lot more useful. Thoughts?

 -Steve
Interestingly I had exactly the same idea a few hours ago as I red the discussions about clear. I just forgot about it without posting. I think it's a good idea. The ignorant destructor (ie w/o parameter) defenition should be inetrnally rewritten to accept an ignored bool parameter. This would make the following thing a doubled and therfore incorrect definition. class X { ~this(){} ~this(bool det){} } If the compiler then adds an overload for 'X.__dtor()' which simply calls 'X.__dtor(false)' [1], we should be totally compatible with existing code. [1] IMO this should be false and not true but I'm not sure.
Aug 10 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 14:56:54 -0400, Mafi <mafi example.org> wrote:

 Am 10.08.2010 20:25, schrieb Steven Schveighoffer:
 One of the common problems of destructors is that they cannot assume any
 of their GC-allocated resources are valid when inside the destrutor.

 The spec says:

 The garbage collector is not guaranteed to run the destructor for all
 unreferenced objects. Furthermore, the order in which the garbage
 collector calls destructors for unreference objects is not specified.
 This means that when the garbage collector calls a destructor for an
 object of a class that has members that are references to garbage
 collected objects, those references may no longer be valid. This means
 that destructors cannot reference sub objects. This rule does not apply
 to auto objects or objects deleted with the DeleteExpression, as the
 destructor is not being run by the garbage collector, meaning all
 references are valid.

 Let's analyze that last sentence (BTW, I think where it says auto, it
 should say scope). "This does not apply to objects deleted with the
 DeleteExpression..." Well, how the hell do you know while in the
 destructor whether you were called via delete/clear or from the GC? You
 don't. So the destructor *must* be written as if you are called from the
 GC, because the GC may be calling it, it is up to the user to determine
 whether your class will be destroyed via delete/clear or the GC. So you
 must assume worst case. Unless you declare your class as scope, which I
 don't even know if anyone does that anymore (I think it's scheduled for
 deprecation anyways).

 But, what if the destructor was given an idea of whether its resources
 are valid or not? Then you could do something different based on that
 input.

 For example, an object that wants to open a file can do so, and in the
 destructor, use the determination of validity to decide whether to close
 the file or not.

 This can be implemented by an optional parameter to the destructor
 that's always passed, but it's not necessary to use it. i.e. you could
 declare your destructor like this:

 ~this(bool deterministic)
 {
 if(deterministic) file.close();
 }

 or like this:

 ~this()
 {
 }

 Scope classes (if allowed to exist) or calls to clear will set
 deterministic to true. The GC will set it to false.

 This would make destructors a lot more useful. Thoughts?

 -Steve
Interestingly I had exactly the same idea a few hours ago as I red the discussions about clear. I just forgot about it without posting. I think it's a good idea. The ignorant destructor (ie w/o parameter) defenition should be inetrnally rewritten to accept an ignored bool parameter. This would make the following thing a doubled and therfore incorrect definition. class X { ~this(){} ~this(bool det){} } If the compiler then adds an overload for 'X.__dtor()' which simply calls 'X.__dtor(false)' [1], we should be totally compatible with existing code.
pushing an ignored argument on the stack doesn't matter. Basically, __dtor would always have a bool argument, and if the definition of the class had ~this(), it doesn't matter. It's the reason you can have main either accept a string array or nothing. -Steve
Aug 10 2010
prev sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Steven Schveighoffer wrote:
...
 This would make destructors a lot more useful.  Thoughts?
 
 -Steve
My first thought was that they are actually two separate functions distinguished by a boolean, then Michel also mentioned the SafeD argument. atm I think it is better to let go of the destructor entirely for anything else than the GC collecting non-gc owned data as we now have. (the unsafe version, not compilable in SafeD). Rather provide a standard interface to implement and base deterministic release of resources on that. A much more simple version of IDisposable. clear() can call this one and leave ~this alone. Anything that needs more reliability will need to use structs / reference counting.
Aug 10 2010
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 15:08:30 -0400, Lutger <lutger.blijdestijn gmail.com>  
wrote:

 Steven Schveighoffer wrote:
 ...
 This would make destructors a lot more useful.  Thoughts?

 -Steve
My first thought was that they are actually two separate functions distinguished by a boolean, then Michel also mentioned the SafeD argument. atm I think it is better to let go of the destructor entirely for anything else than the GC collecting non-gc owned data as we now have. (the unsafe version, not compilable in SafeD). Rather provide a standard interface to implement and base deterministic release of resources on that. A much more simple version of IDisposable. clear() can call this one and leave ~this alone.
Hm... something I just thought of that makes this bad, destructors are special in that they automatically call base destructors. You can't do that with a simple function. But it could be done the way you say (with the caveat that you have to manually call the base method). I think clear should call ~this() in addition to the dispose method because you don't want to have to duplicate code. -Steve
Aug 10 2010
parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Steven Schveighoffer wrote:

 On Tue, 10 Aug 2010 15:08:30 -0400, Lutger <lutger.blijdestijn gmail.com>
 wrote:
 
 Steven Schveighoffer wrote:
 ...
 This would make destructors a lot more useful.  Thoughts?

 -Steve
My first thought was that they are actually two separate functions distinguished by a boolean, then Michel also mentioned the SafeD argument. atm I think it is better to let go of the destructor entirely for anything else than the GC collecting non-gc owned data as we now have. (the unsafe version, not compilable in SafeD). Rather provide a standard interface to implement and base deterministic release of resources on that. A much more simple version of IDisposable. clear() can call this one and leave ~this alone.
Hm... something I just thought of that makes this bad, destructors are special in that they automatically call base destructors. You can't do that with a simple function. But it could be done the way you say (with the caveat that you have to manually call the base method). I think clear should call ~this() in addition to the dispose method because you don't want to have to duplicate code. -Steve
clear could check if the base classes implement the interface and act on that. It already does the same for destructors.
Aug 10 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 10 Aug 2010 16:03:42 -0400, Lutger <lutger.blijdestijn gmail.com>  
wrote:

 Steven Schveighoffer wrote:

 On Tue, 10 Aug 2010 15:08:30 -0400, Lutger  
 <lutger.blijdestijn gmail.com>
 wrote:

 Steven Schveighoffer wrote:
 ...
 This would make destructors a lot more useful.  Thoughts?

 -Steve
My first thought was that they are actually two separate functions distinguished by a boolean, then Michel also mentioned the SafeD argument. atm I think it is better to let go of the destructor entirely for anything else than the GC collecting non-gc owned data as we now have. (the unsafe version, not compilable in SafeD). Rather provide a standard interface to implement and base deterministic release of resources on that. A much more simple version of IDisposable. clear() can call this one and leave ~this alone.
Hm... something I just thought of that makes this bad, destructors are special in that they automatically call base destructors. You can't do that with a simple function. But it could be done the way you say (with the caveat that you have to manually call the base method). I think clear should call ~this() in addition to the dispose method because you don't want to have to duplicate code. -Steve
clear could check if the base classes implement the interface and act on that. It already does the same for destructors.
Checking if a class implements an interface is not as trivial as accessing a member of the classinfo. It's a linear search through the interfaces. Plus, I don't know quite how it would work, I'm not sure its doable. BTW, I assumed that the most derived destructor simply called the base destructor implicitly, I didn't realize this was handled via the runtime, that seems like a low-hanging fruit opportunity for performance improvement... -Steve
Aug 10 2010