www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - destructors

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Is this ok in D?


class MyClass
{
    OtherClass a;
    this() {
       a = new OtherClass;
    }
    ~this() {
       delete a;
    }
}

I was just trying to fix the crash on exit in the tinyXPath code on 
dsource, and it does the above a lot.

--bb
May 01 2007
next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Bill Baxter wrote:
 Is this ok in D?
 
 
 class MyClass
 {
    OtherClass a;
    this() {
       a = new OtherClass;
    }
    ~this() {
       delete a;
    }
 }
 
 I was just trying to fix the crash on exit in the tinyXPath code on
 dsource, and it does the above a lot.
 
 --bb

I'd be suspect of the delete; unless you explicitly delete an object, you cannot guarantee in what order objects will be destroyed. That means that it's entirely possible that 'a' has already been deleted by the time your dtor runs. <Insert song and dance about how having an implicit/explicit flag passed to dtors would solve this.> -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Daniel Keep" <daniel.keep.lists gmail.com> wrote in message 
news:f171sp$104t$1 digitalmars.com...
 <Insert song and dance about how having an implicit/explicit flag passed
 to dtors would solve this.>

<Insert song and dance about how having an explicit destruction order would solve this even easier>
May 01 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Jarrett Billingsley wrote:
 "Daniel Keep" <daniel.keep.lists gmail.com> wrote in message 
 news:f171sp$104t$1 digitalmars.com...
 <Insert song and dance about how having an implicit/explicit flag passed
 to dtors would solve this.>

<Insert song and dance about how having an explicit destruction order would solve this even easier>

<Via the majesty of interpretive dance, point out that having potential cycles of objects makes that really, really difficult, but that having any kind of solution would be an improvement> -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007
prev sibling parent reply Daniel Giddings <daniel.giddings gmail.com> writes:
Bill Baxter wrote:
 Is this ok in D?
 
 
 class MyClass
 {
    OtherClass a;
    this() {
       a = new OtherClass;
    }
    ~this() {
       delete a;
    }
 }
 
 I was just trying to fix the crash on exit in the tinyXPath code on 
 dsource, and it does the above a lot.
 
 --bb

I would expect that to work (it is something I would want to be able to do - otherwise you would need a finalise method - yuck), I can't recall if I have done it though? a should still be detected as having objects referencing it until the MyClass destructor is finished I would think (if the delete a wasn't there). Otherwise you would not be able to rely on any class properties as still existing in its destructor (ie a.method() could cause an exception as well).
May 01 2007
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Daniel Giddings wrote:
 Bill Baxter wrote:
 Is this ok in D?


 class MyClass
 {
    OtherClass a;
    this() {
       a = new OtherClass;
    }
    ~this() {
       delete a;
    }
 }

 I was just trying to fix the crash on exit in the tinyXPath code on
 dsource, and it does the above a lot.

 --bb

I would expect that to work (it is something I would want to be able to do - otherwise you would need a finalise method - yuck), I can't recall if I have done it though? a should still be detected as having objects referencing it until the MyClass destructor is finished I would think (if the delete a wasn't there). Otherwise you would not be able to rely on any class properties as still existing in its destructor (ie a.method() could cause an exception as well).

Exactly. You can't. Long story short is thus: the *only* thing you can say for sure still exists when your destructor is called, is your own object's memory. Look at it like this:
 class ObjPtr
 {
     ObjPtr obj;
     this(ObjPtr obj = null) { this.obj = obj; }
     ~this() { ... }
 }

 auto a = new ObjPtr;
 auto b = new ObjPtr(a);
 auto c = new ObjPtr(b);

 c = null;
 gc.fullCollect();

Question: what order do the objects get destroyed in? Answer: no one knows. The problem is that in order for the GC to destroy objects in "the right order", it would have to build a complete dependency graph between the various objects, then destroy them in the right order. But what happens if you've got a *lot* of objects, or those objects don't care what order they're destroyed in, or worse: cycles. If you've got cycles, the GC is stuffed, and there *is* no correct order to destroy them in. There was a thread quite some time ago that demonstrated a hacked version of phobos that allowed destructors to take a bool argument that told the destructor if the object was being destroyed deterministically (ie: via the delete statement, or because the object was scoped) or not. Sadly, it never caught on :( -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Daniel Keep wrote:
 
 There was a thread quite some time ago that demonstrated a hacked
 version of phobos that allowed destructors to take a bool argument that
 told the destructor if the object was being destroyed deterministically
 (ie: via the delete statement, or because the object was scoped) or not.
  Sadly, it never caught on :(

Tango provides a global hook for this. It isn't exactly the same, but it has a similar effect. Sean
May 01 2007
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Daniel Keep wrote:
 
 Daniel Giddings wrote:
 Bill Baxter wrote:
 Is this ok in D?


 class MyClass
 {
    OtherClass a;
    this() {
       a = new OtherClass;
    }
    ~this() {
       delete a;
    }
 }

 I was just trying to fix the crash on exit in the tinyXPath code on
 dsource, and it does the above a lot.

 --bb

do - otherwise you would need a finalise method - yuck), I can't recall if I have done it though? a should still be detected as having objects referencing it until the MyClass destructor is finished I would think (if the delete a wasn't there). Otherwise you would not be able to rely on any class properties as still existing in its destructor (ie a.method() could cause an exception as well).

Exactly. You can't. Long story short is thus: the *only* thing you can say for sure still exists when your destructor is called, is your own object's memory. Look at it like this:
 class ObjPtr
 {
     ObjPtr obj;
     this(ObjPtr obj = null) { this.obj = obj; }
     ~this() { ... }
 }

 auto a = new ObjPtr;
 auto b = new ObjPtr(a);
 auto c = new ObjPtr(b);

 c = null;
 gc.fullCollect();

Question: what order do the objects get destroyed in? Answer: no one knows. The problem is that in order for the GC to destroy objects in "the right order", it would have to build a complete dependency graph between the various objects, then destroy them in the right order. But what happens if you've got a *lot* of objects, or those objects don't care what order they're destroyed in, or worse: cycles. If you've got cycles, the GC is stuffed, and there *is* no correct order to destroy them in. There was a thread quite some time ago that demonstrated a hacked version of phobos that allowed destructors to take a bool argument that told the destructor if the object was being destroyed deterministically (ie: via the delete statement, or because the object was scoped) or not. Sadly, it never caught on :(

That makes a lot of sense. Why can't it just say that in the documentation? "In the presenence of cycles, it is impossible to ensure that parent objects are always deleted before the children they refer to." Ok, so we can say that generally speaking, any D code that contains a 'delete member;' in the destructor is bad code. Right? The exceptions mentioned in the doc are scope classes (well the doc says "auto" but I think it means old-school auto, aka "scope", because you know that's not going away because of a GC sweep.). And also anything else you know is getting explicitly deleted is ok... except there's no way to *know* if you're being explicitly deleted so you have to assume you're not. --bb
May 01 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Bill Baxter" <dnewsgroup billbaxter.com> wrote in message 
news:f17iv3$1s1a$1 digitalmars.com...
 Ok, so we can say that generally speaking, any D code that contains a 
 'delete member;' in the destructor is bad code.  Right?

Any destructor which uses any reference members in any way is bad code. Thus making destructors nearly worthless for anything but releasing system resources. But even then, I'm not sure if the destructors always get called, especially at the end of the program.
May 01 2007
parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
Jarrett Billingsley wrote:
 "Bill Baxter" <dnewsgroup billbaxter.com> wrote in message 
 news:f17iv3$1s1a$1 digitalmars.com...
 Ok, so we can say that generally speaking, any D code that contains a 
 'delete member;' in the destructor is bad code.  Right?

Any destructor which uses any reference members in any way is bad code.

Why so? If a reference member is manually memory-managed, it's quite ok (and expected) to have it being deleted in the constructor, no? -- Bruno Medeiros - MSc in CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
May 03 2007
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Bruno Medeiros" <brunodomedeiros+spam com.gmail> wrote in message 
news:f1ceq9$1o79$1 digitalmars.com...
 Why so? If a reference member is manually memory-managed, it's quite ok 
 (and expected) to have it being deleted in the constructor, no?

Yes, that's true.
May 03 2007
prev sibling parent reply Daniel Giddings <danielg microforte.com> writes:
Daniel Keep wrote:
 
 
 Exactly.  You can't.
 
 Long story short is thus: the *only* thing you can say for sure still
 exists when your destructor is called, is your own object's memory.

Yuck - I never really thought about this. So destructors in D are almost as useless as the __del__ method in Python :-(
 Question: what order do the objects get destroyed in?
 Answer: no one knows.
 
 The problem is that in order for the GC to destroy objects in "the right
 order", it would have to build a complete dependency graph between the
 various objects, then destroy them in the right order.
 
 But what happens if you've got a *lot* of objects, or those objects
 don't care what order they're destroyed in, or worse: cycles.  If you've
 got cycles, the GC is stuffed, and there *is* no correct order to
 destroy them in.
 
 There was a thread quite some time ago that demonstrated a hacked
 version of phobos that allowed destructors to take a bool argument that
 told the destructor if the object was being destroyed deterministically
 (ie: via the delete statement, or because the object was scoped) or not.
  Sadly, it never caught on :(
 
 	-- Daniel
 

The bool would be incredibly handy as you'd be able to still do destructor type things safely with explicit deletes. I suppose an alternative would be something like class MyClass { OtherClass a; this() { a = new OtherClass; } MyClass destroy() { delete a; delete this; return null; } // is the return null above ok in D? - it seemed ok when run } then you'd know when its been explicitly deleted. MyClass m = new MyClass; ... m = m.destroy();
May 01 2007
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Daniel Giddings wrote:
 Daniel Keep wrote:
 Exactly.  You can't.

 Long story short is thus: the *only* thing you can say for sure still
 exists when your destructor is called, is your own object's memory.

Yuck - I never really thought about this. So destructors in D are almost as useless as the __del__ method in Python :-(
 Question: what order do the objects get destroyed in?
 Answer: no one knows.

 The problem is that in order for the GC to destroy objects in "the right
 order", it would have to build a complete dependency graph between the
 various objects, then destroy them in the right order.

 But what happens if you've got a *lot* of objects, or those objects
 don't care what order they're destroyed in, or worse: cycles.  If you've
 got cycles, the GC is stuffed, and there *is* no correct order to
 destroy them in.

 There was a thread quite some time ago that demonstrated a hacked
 version of phobos that allowed destructors to take a bool argument that
 told the destructor if the object was being destroyed deterministically
 (ie: via the delete statement, or because the object was scoped) or not.
  Sadly, it never caught on :(

     -- Daniel

The bool would be incredibly handy as you'd be able to still do destructor type things safely with explicit deletes. I suppose an alternative would be something like class MyClass { OtherClass a; this() { a = new OtherClass; } MyClass destroy() { delete a; delete this; return null; } // is the return null above ok in D? - it seemed ok when run } then you'd know when its been explicitly deleted. MyClass m = new MyClass; ... m = m.destroy();

I was thinking that too. That might be ok for some use cases, but it doesn't help with 'scope' instances (the destructor will get called not your 'destroy()' method). And in generic cases, like a tuple of objects you want to destroy, the generic function has no way of knowing that it should call 'obj.destroy()' on some objects instead of 'delete obj'. --bb
May 01 2007
parent reply Daniel Giddings <danielg microforte.com> writes:
Bill Baxter wrote:
 
 I was thinking that too.  That might be ok for some use cases, but it 
 doesn't help with 'scope' instances (the destructor will get called not 
 your 'destroy()' method).  And in generic cases, like a tuple of objects 
 you want to destroy, the generic function has no way of knowing that it 
 should call 'obj.destroy()' on some objects instead of 'delete obj'.
 
 
 --bb

With a small change you could catch those cases, but it is starting to get a bit messy. class MyClass { OtherClass a; bool destroyed = false; this() { a = new OtherClass; } ~this() { if( !destroyed ) throw new Exception( "~this without .destroy" ); } MyClass destroy() { delete a; destroyed = true; delete this; return null; } } :-) Dan
May 01 2007
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Daniel Giddings wrote:
 Bill Baxter wrote:
 I was thinking that too.  That might be ok for some use cases, but it
 doesn't help with 'scope' instances (the destructor will get called
 not your 'destroy()' method).  And in generic cases, like a tuple of
 objects you want to destroy, the generic function has no way of
 knowing that it should call 'obj.destroy()' on some objects instead of
 'delete obj'.


 --bb

With a small change you could catch those cases, but it is starting to get a bit messy. class MyClass { OtherClass a; bool destroyed = false; this() { a = new OtherClass; } ~this() { if( !destroyed ) throw new Exception( "~this without ..destroy" ); } MyClass destroy() { delete a; destroyed = true; delete this; return null; } } :-) Dan

I thought exceptions thrown in a destructor got ignored? -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007
parent Daniel Giddings <danielg microforte.com> writes:
 
 I thought exceptions thrown in a destructor got ignored?
 
 	-- Daniel
 

The test below gives the following result: this ~this Error: ~this without .destroy at least with dmd 1.007 in windows... :-) Dan -- import std.stdio; class OtherClass { this() { writefln( "this" ); } ~this() { writefln( "~this" ); } } class MyClass { OtherClass a; bool destroyed = false; this() { a = new OtherClass; } ~this() { if( !destroyed ) throw new Exception( "~this without .destroy" ); } MyClass destroy() { delete a; destroyed = true; delete this; return null; } } void main() { MyClass m = new MyClass; }
May 01 2007
prev sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Daniel Giddings wrote:
 Daniel Keep wrote:
 Exactly.  You can't.

 Long story short is thus: the *only* thing you can say for sure still
 exists when your destructor is called, is your own object's memory.

Yuck - I never really thought about this. So destructors in D are almost as useless as the __del__ method in Python :-(
 Question: what order do the objects get destroyed in?
 Answer: no one knows.

 The problem is that in order for the GC to destroy objects in "the right
 order", it would have to build a complete dependency graph between the
 various objects, then destroy them in the right order.

 But what happens if you've got a *lot* of objects, or those objects
 don't care what order they're destroyed in, or worse: cycles.  If you've
 got cycles, the GC is stuffed, and there *is* no correct order to
 destroy them in.

 There was a thread quite some time ago that demonstrated a hacked
 version of phobos that allowed destructors to take a bool argument that
 told the destructor if the object was being destroyed deterministically
 (ie: via the delete statement, or because the object was scoped) or not.
  Sadly, it never caught on :(

     -- Daniel

The bool would be incredibly handy as you'd be able to still do destructor type things safely with explicit deletes. I suppose an alternative would be something like class MyClass { OtherClass a; this() { a = new OtherClass; } MyClass destroy() { delete a; delete this; return null; } // is the return null above ok in D? - it seemed ok when run } then you'd know when its been explicitly deleted. MyClass m = new MyClass; .... m = m.destroy();

The other thing I started doing in some of my code was to use the following: interface Disposable { void dispose(); } void dispose(Object obj) { if( auto dobj = cast(Disposable)obj ) dobj.dispose(); } Yes, you have to do it manually, but at least you can do it *safely*. I think Tango has something similar :P -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Daniel Keep wrote:
 
 Daniel Giddings wrote:
 Daniel Keep wrote:

interface Disposable { void dispose(); } void dispose(Object obj) { if( auto dobj = cast(Disposable)obj ) dobj.dispose(); }

Why not follow up the call to dispose with a 'delete dobj'? Would that make 'scope' fail? --bb
May 01 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Bill Baxter wrote:
 Daniel Keep wrote:
 Daniel Giddings wrote:
 Daniel Keep wrote:

following: interface Disposable { void dispose(); } void dispose(Object obj) { if( auto dobj = cast(Disposable)obj ) dobj.dispose(); }

Why not follow up the call to dispose with a 'delete dobj'? Would that make 'scope' fail? --bb

Then you'd get a double-free, so I think it might. I actually forgot about the *other* function I use: void destroy(inout Object obj) { dispose(obj); delete obj; } So you can destroy heap allocated objects, and just dispose scoped ones. -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 01 2007