www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - destructor order

reply Albin Breen <abreen ea.com> writes:
Hi! I've been trying out D recently but I'm a little confused about the order
in which destructors are called:

class A {
	this() { writeln("ctor"); }
	~this() { writeln("dtor"); }

	static this() { writeln("static ctor"); }
	static ~this() { writeln("static dtor"); }
}

void main() {
	auto a = new A;
}

This will output:
static ctor
ctor
static dtor
dtor

I would have thought that the static destructor should be the last one (in case
there are static dependencies). Have I missed something?

/Albin
Jan 26 2011
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 26 Jan 2011 07:55:22 -0500, Albin Breen <abreen ea.com> wrote:

 Hi! I've been trying out D recently but I'm a little confused about the  
 order in which destructors are called:

 class A {
 	this() { writeln("ctor"); }
 	~this() { writeln("dtor"); }

 	static this() { writeln("static ctor"); }
 	static ~this() { writeln("static dtor"); }
 }

 void main() {
 	auto a = new A;
 }

 This will output:
 static ctor
 ctor
 static dtor
 dtor

 I would have thought that the static destructor should be the last one  
 (in case there are static dependencies). Have I missed something?

dtors are not guaranteed to run in any order. See here: http://www.digitalmars.com/d/2.0/class.html#destructors "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." FYI, the runtime calls static ctors when a thread starts (and once at program start for the main thread) and calls static dtors when a thread exits. If you want a static ctor/dtor to initialize/destroy shared data (or __gshared), use a shared static ctor/dtor. -Steve
Jan 26 2011
next sibling parent reply Albin Breen <abreen ea.com> writes:
Steven Schveighoffer Wrote:

 See here: http://www.digitalmars.com/d/2.0/class.html#destructors
 
 "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."

Thanks! This means that the GC cannot be trusted to call destructors. I interpret that as "class destructors must be called manually". Furthermore, The D Programming Language book states that: "...there is no delete operator. (D used to have a delete operator, but it was depre- cated.)" so you can't use that either. In other words you are left with: clear(a); to manually call the destructor, which will also call the constructor again (this time with no parameters), and possibly (but not certainly) the destructor once more. To be able to use clear() you will have to enforce RAII using structs (not garbage collected) or finally{} or scope constructs all the way from main in parallel with all your garbage collected code. All in all, if class destructors cannot be guaranteed to execute by other means than the manual approach then should they be considered a liability? In Phobos std.socket a destructor is used to close() the socket. /Albin
Jan 26 2011
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/26/11 10:17 AM, Steven Schveighoffer wrote:
 On Wed, 26 Jan 2011 11:09:45 -0500, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:

 I think I glanced over a recent svn commit that fixed this, though I'm
 not sure.

No, it hasn't been fixed. The fix is *really* simple, just have clear call rt_finalize (as Sean pointed out on the mailing list) -Steve

Steve, if you could point out what I need to do I'll be glad to do it right now. Better yet, feel free to try your hand at a git commit. It's fun! Andrei
Jan 26 2011
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/26/11 12:18 PM, Steven Schveighoffer wrote:
 On Wed, 26 Jan 2011 12:26:22 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 1/26/11 10:17 AM, Steven Schveighoffer wrote:
 On Wed, 26 Jan 2011 11:09:45 -0500, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:

 I think I glanced over a recent svn commit that fixed this, though I'm
 not sure.

No, it hasn't been fixed. The fix is *really* simple, just have clear call rt_finalize (as Sean pointed out on the mailing list) -Steve

Steve, if you could point out what I need to do I'll be glad to do it right now. Better yet, feel free to try your hand at a git commit. It's fun!

*sweats nervously* I don't know, I'd like to read about how git works before doing a commit. I don't really understand it at all, I had the same problem with subversion when I started using it. The fix is really easy, just change clear to this: void clear(T)(T obj) if (is(T == class)) { rt_finalize(cast(void*)obj); } Here is the body of the current clear: if (!obj) return; auto ci = obj.classinfo; auto defaultCtor = cast(void function(Object)) ci.defaultConstructor; version(none) // enforce isn't available in druntime _enforce(defaultCtor || (ci.flags & 8) == 0); immutable size = ci.init.length; auto ci2 = ci; do { auto dtor = cast(void function(Object))ci2.destructor; if (dtor) dtor(obj); ci2 = ci2.base; } while (ci2) auto buf = (cast(void*) obj)[0 .. size]; buf[] = ci.init; if (defaultCtor) defaultCtor(obj); And the body of rt_finalize if (p) // not necessary if called from gc { ClassInfo** pc = cast(ClassInfo**)p; if (*pc) { ClassInfo c = **pc; byte[] w = c.init; try { if (det || collectHandler is null || collectHandler(cast(Object)p)) { do { if (c.destructor) { fp_t fp = cast(fp_t)c.destructor; (*fp)(cast(Object)p); // call destructor } c = c.base; } while (c); } if ((cast(void**)p)[1]) // if monitor is not null _d_monitordelete(cast(Object)p, det); (cast(byte*) p)[0 .. w.length] = w[]; } catch (Throwable e) { onFinalizeError(**pc, e); } finally { *pc = null; // zero vptr } } } Note the eerie similarities :) -Steve

Thanks for taking this to completion! I had it on my todo list forever. Andrei
Feb 26 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 26 Jan 2011 10:53:29 -0500, Albin Breen <abreen ea.com> wrote:

 Steven Schveighoffer Wrote:

 See here: http://www.digitalmars.com/d/2.0/class.html#destructors

 "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."

Thanks! This means that the GC cannot be trusted to call destructors. I interpret that as "class destructors must be called manually". Furthermore, The D Programming Language book states that: "...there is no delete operator. (D used to have a delete operator, but it was depre- cated.)" so you can't use that either. In other words you are left with: clear(a); to manually call the destructor, which will also call the constructor again (this time with no parameters), and possibly (but not certainly) the destructor once more. To be able to use clear() you will have to enforce RAII using structs (not garbage collected) or finally{} or scope constructs all the way from main in parallel with all your garbage collected code.

That is a misdesign in clear. It should destroy an object and not initialize it again. Furthermore, the destructor should only be called once. Andrei agreed to make that change, but it hasn't gone in yet. -Steve
Jan 26 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
I think I glanced over a recent svn commit that fixed this, though I'm not sure.
Jan 26 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 26 Jan 2011 11:09:45 -0500, Andrej Mitrovic  
<andrej.mitrovich gmail.com> wrote:

 I think I glanced over a recent svn commit that fixed this, though I'm  
 not sure.

No, it hasn't been fixed. The fix is *really* simple, just have clear call rt_finalize (as Sean pointed out on the mailing list) -Steve
Jan 26 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 26 Jan 2011 12:26:22 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 1/26/11 10:17 AM, Steven Schveighoffer wrote:
 On Wed, 26 Jan 2011 11:09:45 -0500, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:

 I think I glanced over a recent svn commit that fixed this, though I'm
 not sure.

No, it hasn't been fixed. The fix is *really* simple, just have clear call rt_finalize (as Sean pointed out on the mailing list) -Steve

Steve, if you could point out what I need to do I'll be glad to do it right now. Better yet, feel free to try your hand at a git commit. It's fun!

*sweats nervously* I don't know, I'd like to read about how git works before doing a commit. I don't really understand it at all, I had the same problem with subversion when I started using it. The fix is really easy, just change clear to this: void clear(T)(T obj) if (is(T == class)) { rt_finalize(cast(void*)obj); } Here is the body of the current clear: if (!obj) return; auto ci = obj.classinfo; auto defaultCtor = cast(void function(Object)) ci.defaultConstructor; version(none) // enforce isn't available in druntime _enforce(defaultCtor || (ci.flags & 8) == 0); immutable size = ci.init.length; auto ci2 = ci; do { auto dtor = cast(void function(Object))ci2.destructor; if (dtor) dtor(obj); ci2 = ci2.base; } while (ci2) auto buf = (cast(void*) obj)[0 .. size]; buf[] = ci.init; if (defaultCtor) defaultCtor(obj); And the body of rt_finalize if (p) // not necessary if called from gc { ClassInfo** pc = cast(ClassInfo**)p; if (*pc) { ClassInfo c = **pc; byte[] w = c.init; try { if (det || collectHandler is null || collectHandler(cast(Object)p)) { do { if (c.destructor) { fp_t fp = cast(fp_t)c.destructor; (*fp)(cast(Object)p); // call destructor } c = c.base; } while (c); } if ((cast(void**)p)[1]) // if monitor is not null _d_monitordelete(cast(Object)p, det); (cast(byte*) p)[0 .. w.length] = w[]; } catch (Throwable e) { onFinalizeError(**pc, e); } finally { *pc = null; // zero vptr } } } Note the eerie similarities :) -Steve
Jan 26 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, January 26, 2011 07:53:29 Albin Breen wrote:
 Steven Schveighoffer Wrote:
 See here: http://www.digitalmars.com/d/2.0/class.html#destructors
 
 "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."

Thanks! This means that the GC cannot be trusted to call destructors. I interpret that as "class destructors must be called manually". Furthermore, The D Programming Language book states that: "...there is no delete operator. (D used to have a delete operator, but it was depre- cated.)" so you can't use that either. In other words you are left with: clear(a); to manually call the destructor, which will also call the constructor again (this time with no parameters), and possibly (but not certainly) the destructor once more. To be able to use clear() you will have to enforce RAII using structs (not garbage collected) or finally{} or scope constructs all the way from main in parallel with all your garbage collected code. All in all, if class destructors cannot be guaranteed to execute by other means than the manual approach then should they be considered a liability? In Phobos std.socket a destructor is used to close() the socket.

Personally, I don't think that using class destructors is currently a good idea. Certainly, if you _need_ the destructor to run, then you probably need to rethink your design, since there's no guarantee that it's going to run. You can call clear(), but relying on your code doing that in the general case seems like a bad idea. Already, class destructors can't deal with other GC-allocated objects, so they're restricted to cleaning up other types of resources. If anything, I question that classes should even _have_ destructors. For structs, it makes great sense, but for classes... not so much. At best, they seem useable for stuff like file descriptors where you'd like them to be cleaned up when the object is destroyed if you forgot to do so by calling the appropriate close function or whatnot. But generally, it would be a bug in your code if you didn't release the file descriptor yourself (via the close function or whatever) So, I'd argue that in the general case, you shouldn't be using class destructors. If your object really needs a destructor, then perhaps it should be a struct (on the stack, since structs on the heap _never_ get their destructors called IIRC). - Jonathan M Davis
Jan 26 2011
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 26 Jan 2011 13:18:58 -0500, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:

 On Wed, 26 Jan 2011 12:26:22 -0500, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:

 On 1/26/11 10:17 AM, Steven Schveighoffer wrote:
 On Wed, 26 Jan 2011 11:09:45 -0500, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:

 I think I glanced over a recent svn commit that fixed this, though I'm
 not sure.

No, it hasn't been fixed. The fix is *really* simple, just have clear call rt_finalize (as Sean pointed out on the mailing list) -Steve

Steve, if you could point out what I need to do I'll be glad to do it right now. Better yet, feel free to try your hand at a git commit. It's fun!

*sweats nervously* I don't know, I'd like to read about how git works before doing a commit. I don't really understand it at all, I had the same problem with subversion when I started using it. The fix is really easy, just change clear to this: void clear(T)(T obj) if (is(T == class)) { rt_finalize(cast(void*)obj); }

Finally got around to doing this. I have to agree with others, git is freaking cool (and github too). Thanks to Lars for suggesting the Pro Git book, it helped me a lot. https://github.com/D-Programming-Language/druntime/pull/7 -Steve
Feb 24 2011
prev sibling parent Sean Kelly <sean invisibleduck.org> writes:
On Jan 26, 2011, at 4:55 AM, Albin Breen wrote:

 Hi! I've been trying out D recently but I'm a little confused about =

=20
 class A {
 	this() { writeln("ctor"); }
 	~this() { writeln("dtor"); }
=20
 	static this() { writeln("static ctor"); }
 	static ~this() { writeln("static dtor"); }
 }
=20
 void main() {
 	auto a =3D new A;
 }
=20
 This will output:
 static ctor
 ctor
 static dtor
 dtor
=20
 I would have thought that the static destructor should be the last one =

The static dtors are run when main exits and then the GC terminates, = which may trigger a final collection. In this case, the last "dtor" is = when this collection occurs.=
Jan 26 2011