digitalmars.D.bugs - Destructor Bug?
- AJG (14/14) Jul 30 2005 Hi,
- Uwe Salomon (8/20) Jul 31 2005 http://www.digitalmars.com/d/class.html#destructors
- AJG (11/17) Jul 31 2005 Is there any way around this? I'd like to close a database connection up...
- Uwe Salomon (12/14) Jul 31 2005 I had a similar problem when implementing signals&slots. You can create ...
- AJG (20/32) Jul 31 2005 Hm... maybe this could work. What's the equivalent code using malloc? I ...
- Uwe Salomon (25/27) Aug 01 2005 Hmm, as it is legacy code, i fear that it is not possible. You can look ...
- Uwe Salomon (11/19) Jul 31 2005 I can only think of the following solutions:
- AJG (11/28) Jul 31 2005 I wonder whether it makes it trivially more difficult or not. It could a...
- Dave (21/39) Jul 31 2005 Would something like this cause a complete re-write for you?
- AJG (17/34) Jul 31 2005 The problem is that the object I'm using is not my own. I'm using Jeremy
- Manfred Nowak (9/12) Jul 31 2005 Why is it not possible to embed the foreign object into a class with
- AJG (12/18) Jul 31 2005 Because it's not just one type of object. It's a whole collection of pos...
- Manfred Nowak (12/29) Jul 31 2005 I do not understand this argument. If the subclasses represent
- Regan Heath (2/5) Jul 31 2005 Or use the "auto" keyword.
- AJG (12/40) Aug 01 2005 They all override a virtual void close(); finalization method to termina...
- Manfred Nowak (6/11) Aug 01 2005 We need not try to explore this any further because it will not yield
- Ben Hinkle (11/17) Aug 01 2005 I agree destructors in D are very limited. They seem to be only useful f...
- Russ Lewis (47/73) Aug 01 2005 The issue here is that when 2 objects become garbage at the same time,
- Walter (3/3) Aug 02 2005 Generally speaking, destructors called by the garbage collector are not
- AJG (6/9) Aug 02 2005 What about getting the compiler to complain if there are sub-object memb...
- Uwe Salomon (5/14) Aug 02 2005 Hmm, but sub-object references are allowed for auto objects and objects ...
- AJG (6/19) Aug 02 2005 Yeah, but those would be fine. Auto-objects would be allowed. Delete Obj...
- Uwe Salomon (23/30) Aug 02 2005 Yep. But how should the compiler know that?
- AJG (16/45) Aug 02 2005 Ohhhhh... I see what you mean now. So if you call explicit delete on the...
- Uwe Salomon (1/5) Aug 02 2005 Exactly. Perhaps this would be a solution to your problem?
- Ben Hinkle (44/48) Aug 02 2005 This got me thinking about the uses of auto classes I've seen in D so fa...
- Holger (4/52) Aug 02 2005 Neat! I like the idea.
Hi, I took me a while to find and reduce this to a minimal example. I was going nuts thinking it was my database abstraction layer. I think it's a bug in D. Segmentation Fault w/ DMD 0.128 on linux. Cheers, --AJG.
Jul 30 2005
I took me a while to find and reduce this to a minimal example. I was going nuts thinking it was my database abstraction layer. I think it's a bug in D. Segmentation Fault w/ DMD 0.128 on linux.http://www.digitalmars.com/d/class.html#destructors "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 are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour. Ciao uwe
Jul 31 2005
Hi,http://www.digitalmars.com/d/class.html#destructors "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 are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour.Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me. In addition, what's the point of a destructor that's can't access sub-objects? How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)? Is there a preferred way of doing that right now? Thanks for the help! --AJG.
Jul 31 2005
Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me.I had a similar problem when implementing signals&slots. You can create the sub-object on the C heap with malloc(). Or close the connection in the sub-object's destructor. This problem can happen if there are difficult relations between objects, but mostly it shows imperfect design, i think. If a class owns a child, and it has to call finalization routines of it when being destructed, that somehow means that the child does not fully take care of its own destruction, right? So it does not fully protect its invariant. Either that should be changed, or if that is not possible a sub-struct should be used, and the parent class takes care of protecting the invariant. Ciao uwe
Jul 31 2005
Hi,Hm... maybe this could work. What's the equivalent code using malloc? I mean, how do you call the object's constructor(s) using this method?Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me.I had a similar problem when implementing signals&slots. You can create the sub-object on the C heap with malloc(). Or close the connection in the sub-object's destructor.This problem can happen if there are difficult relations between objects, but mostly it shows imperfect design, i think. If a class owns a child, and it has to call finalization routines of it when being destructed, that somehow means that the child does not fully take care of its own destruction, right? So it does not fully protect its invariant. Either that should be changed, or if that is not possible a sub-struct should be used, and the parent class takes care of protecting the invariant.It very well might be a problem in one class' design. However, just because this is the case, doesn't mean we shouldn't be allowed to do it. We should be allowed, precisely to fix this kind of problem when the code is not ours. What about legacy code and libraries? Sometimes these things don't follow ideal OO design, and as it stands, we can't properly fix it in the destructor. In addition, what about when clean-up isn't that simple? What if there are options that need to be specified? As a trivial example, you could have: void DB::close(bool commit) { if (commit) m_Conn.commit(); m_Conn.close(); } Sure, you could move those parameters to the constructor as AutoCommit, but why move finalization logic to the constructor? IMO that's why we have destructors, to fully take care of cleanup for objects that don't do it easily and automatically. Thanks for the help, --AJG.
Jul 31 2005
Hm... maybe this could work. What's the equivalent code using malloc? I mean, how do you call the object's constructor(s) using this method?Hmm, as it is legacy code, i fear that it is not possible. You can look into http://www.digitalmars.com/techtips/class_objects.html But this is more like a hack. The correct way would be to overload operator new and delete for the class. See http://www.digitalmars.com/d/memory.html#newdelete And you are right, in this case there is really a need for the destructor to access sub-objects. Anyways, imagine two objects that are "linked together": class A { B b; } class B { A a; } One of them has to be deleted first. Which one, that is undetermined. Thus you cannot be sure to be able to access b in A's destructor and vice versa. The GC would have to examine this kind of relationships, and they could be much more complex. That's the reason for the current rule. Another solution, by the way: use delete on your object. In this case you *can* reference sub-objects. Ciao uwe
Aug 01 2005
To answer your other question fully:In addition, what's the point of a destructor that's can't access sub-objects?It would make the job of the garbage collector more difficult, i guess.How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)?I can only think of the following solutions: * Allocate the objects somewhere else than the GC heap. * Use structs. This is somehow similar to the next one: * Change the design. Example: a file object could have a "autoClose" property that is set to true by the parent if necessary, and the file destructor automatically closes the attached handle in this case. The third one should be the preferred, of course. Ciao uwe
Jul 31 2005
Hi,To answer your other question fully:I wonder whether it makes it trivially more difficult or not. It could also be that Walter specifially doesn't want that kind of behaviour in the destructor (for philosophical reasons, a la opAssign). Although that would just be mean. ;) destructor, right?In addition, what's the point of a destructor that's can't access sub-objects?It would make the job of the garbage collector more difficult, i guess.Yeah, this could be an option. I posted a question about this on the other message.How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)?I can only think of the following solutions: * Allocate the objects somewhere else than the GC heap.* Use structs. This is somehow similar to the next one: * Change the design. Example: a file object could have a "autoClose" property that is set to true by the parent if necessary, and the file destructor automatically closes the attached handle in this case. The third one should be the preferred, of course.Unfortunately, I can't do such a thing because it's not my code. Thanks for the help, --AJG.
Jul 31 2005
In article <dcj0gn$c7r$1 digitaldaemon.com>, AJG says...Hi,Would something like this cause a complete re-write for you? #class DBConn #class Test #void main() { new Test(); } I would think this would be the 'preferred' pattern since (IMHO) an object like DBConn should always clean-up after itself anyhow. Calling delete on the object is allowed, the object just can't be dereferenced in the parent dtor. - Davehttp://www.digitalmars.com/d/class.html#destructors "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 are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour.Is there any way around this? I'd like to close a database connection upon destruction, but this won't let me. In addition, what's the point of a destructor that's can't access sub-objects? How would you do clean-up operations that are not strictly memory-related? Like clearing a mutex, or releasing a lock on a shared resource (such as a hardware device), or performing database finalization (closing connections, committing changes, etc.)? Is there a preferred way of doing that right now? Thanks for the help! --AJG.
Jul 31 2005
Hi,Would something like this cause a complete re-write for you? #class DBConn #class Test #void main() { new Test(); } I would think this would be the 'preferred' pattern since (IMHO) an object like DBConn should always clean-up after itself anyhow.The problem is that the object I'm using is not my own. I'm using Jeremy Cowgar's DDBI, which has explicit connect() and close() functions, and no destructor. What I had before worked because I inherited from the object, and I could call close() as my own function in my own destructor. But this forced me to inherit from a specific DB, like MySQL. To enable support for any DB type (PG, SQLite, etc.), I need to contain the database as a sub-object, and then create it depending on a parameter. When I made this change, the segmentation faults started because of the problem in the destructor. I could hack DDBI to auto-close the connection, but I'd rather not, since that's not its interface. In my opinion destructors are broken, and very limited. We should be able to reference sub-objects that don't do automatic cleanup precisely for cases like this. Thanks for the help. --AJG.
Jul 31 2005
AJG <AJG_member pathlink.com> wrote: [...]In my opinion destructors are broken, and very limited. We should be able to reference sub-objects that don't do automatic cleanup precisely for cases like this.Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object? Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed. -manfred
Jul 31 2005
Hi,Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.Yeah, it's a possible workaround. At any rate, if you are doing this, it's essentially a hack to bypass the normal destructor restrictions, which shouldn't be there in the first place. Cheers, --AJG.
Jul 31 2005
AJG <AJG_member pathlink.com> wrote:I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.No it is not a workaround as I found out: | The garbage collector is not guaranteed to run the destructor | for all unreferenced objects. And this restrictions follows in a natural way from the GC.Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.Yeah, it's a possible workaround.bypass the normal destructor restrictions, which shouldn't be there in the first place.Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely. -manfred
Jul 31 2005
Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely.Or use the "auto" keyword. Regan
Jul 31 2005
Hi,They all override a virtual void close(); finalization method to terminate the connection. In order for your solution to work, I'd have to wrap up (inherit from) every subclass in another object that calls this method as a protected member, whereas if I could reference the object in the destructor, I could just call .close() once in one single object. I don't know if that makes sense. If it doesn't could you provide an example of what you mean?I do not understand this argument. If the subclasses represent different database types, then why is the cleanup procedure the same in all cases?Why is it not possible to embed the foreign object into a class with a subclass, whose destrcutor calls the cleanup procedure of the foreign object?Because it's not just one type of object. It's a whole collection of possible sub-classes of a BaseDatabase class.Oh, great. Yet another restriction.No it is not a workaround as I found out: | The garbage collector is not guaranteed to run the destructor | for all unreferenced objects.Then because the subclass' destructor is executed before the embedding class' destructor the cleanup procedure will be executed before the foreign object is destroyed.Yeah, it's a possible workaround.And this restrictions follows in a natural way from the GC.Couldn't the GC just tag the objects that are needed and keep those from being collected until their containers' run their respective destructors? Cheers, --AJG.bypass the normal destructor restrictions, which shouldn't be there in the first place.Because of the GC they must be there and if you want to guarantee the execution of the cleanup operation you have to resort to deleting explictely.
Aug 01 2005
AJG <AJG_member pathlink.com> wrote:I don't know if that makes sense. If it doesn't could you provide an example of what you mean?We need not try to explore this any further because it will not yield a solution.Couldn't the GC just tag the objects that are needed and keep those from being collected until their containers' run their respective destructors?D is built around the type of GC currently used. Especially tagging does not help in case of cyclic references. -manfred
Aug 01 2005
I could hack DDBI to auto-close the connection, but I'd rather not, since that's not its interface. In my opinion destructors are broken, and very limited. We should be able to reference sub-objects that don't do automatic cleanup precisely for cases like this.I agree destructors in D are very limited. They seem to be only useful for a class that owns an external resource that must be destroyed when the object is destroyed. To me that's ok if it gives the GC enough freedom to be faster and more predictable. I can see how one could say dtors in D are broken if that own an external resource do not have dtors then I'd request one from the author as a safety net. A user class holding a reference to a Database should not have a dtor since those classes do not own the external resource. In any case if the database dtor ends up closing the external resource it could very well be a bug since the user code should have closed the database long before the dtor ended up running.
Aug 01 2005
The issue here is that when 2 objects become garbage at the same time, there is no defined order in which they are cleaned up. So, sometimes, the code like you wrote below will work, because the holder of the reference is destroyed first; other times, it won't, because they are cleaned up in the opposite order. The reason that this problem exists is because the GC must deal with loops; when a loop of object references goes out of scope, what gets deleted first? *** 'auto' solution *** One solution is to use 'auto' objects. http://digitalmars.com/d/class.html says that auto objects don't have this ordering issue. However, I don't think that you can reassign 'auto' objects, so this is not an option if you will sometimes have to change the reference variable. *** manual refcounting solution *** Another solution is to find a way to make sure that the refered-to object is not garbage until after the referer dies; one way to do that is with an outside reference. The code example below has an associative array which stores references to objects in a global; these references can be added to or removed as needed, to ensure ordering: uint[Object] referenceTable; void AddRef(Object o) { // you can write more efficient code (avoid the double lookup in // the AA) but this is easy to read for learning purposes. Also, // this code is not thread-safe. if(o in referenceTable) referenceTable[o]++; else referenceTable[o] = 1; } void DelRef(Object o) { // again, double-lookup and thread safety issues. assert(o in referenceTable); referenceTable[o]--; if(referenceTable[o] == 0) referenceTable.remove(o); // don't delete, since somebody else in the world // might still have a reference. Just drop this // reference, making it possible that this *might* // be garbage on the next pass. } class Test { Object obj = null; this () { obj = new Object(); AddRef(obj); } ~this() { obj.toString(); DelRef(obj); } } Uwe Salomon wrote:I took me a while to find and reduce this to a minimal example. I was going nuts thinking it was my database abstraction layer. I think it's a bug in D. Segmentation Fault w/ DMD 0.128 on linux.http://www.digitalmars.com/d/class.html#destructors "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 are no longer valid. This means that destructors cannot reference sub objects." So this is documented and correct behaviour. Ciao uwe
Aug 01 2005
Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.
Aug 02 2005
Hi Walter,Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement. Thanks, --AJG.
Aug 02 2005
Hmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :( Ciao uweGenerally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement.
Aug 02 2005
Hi,Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right? Cheers, --AJG.Hmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :(Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.What about getting the compiler to complain if there are sub-object member references in the destructor of a non-auto object, since they are not allowed? Would that be possible? It'd be an improvement.
Aug 02 2005
Yep. But how should the compiler know that? class SomeClass { SomeOtherClass m_cl; // ... ~this() { m_cl.doSomething(); } } SomeClass[] array; int main() { array ~= new SomeClass; array ~= new SomeClass; delete array[0]; } In this case the compiler would have to conclude that the second object in the array will be found by the GC. How to do that during compilation time? Ciao uweHmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :(Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right?
Aug 02 2005
Hi,Yep. But how should the compiler know that? class SomeClass { SomeOtherClass m_cl; // ... ~this() { m_cl.doSomething(); } } SomeClass[] array; int main() { array ~= new SomeClass; array ~= new SomeClass; delete array[0]; }Hmm, but sub-object references are allowed for auto objects and objects that are explicitly deleted... This somehow seems like a design weakness to me. :(Yeah, but those would be fine. Auto-objects would be allowed. Delete Object would be allowed. Everything else (which is illegal, if I'm not mistaken), is disallowed. Right?In this case the compiler would have to conclude that the second object in the array will be found by the GC. How to do that during compilation time?Ohhhhh... I see what you mean now. So if you call explicit delete on the _super_ object, then its destructor can safely access sub-object references, is that correct? That's just confusing and error-prone in my opinion. So depending on how it's deleted (GC vs. explicit) an object might (a) work perfectly, or (b) crash hard with a segmentation fault. I don't think that kind of design (and the behaviour that it causes) should be encouraged. I'm not sure what the compiler can do but I think it's a problem that should be thought about. I think (for now) maybe the compiler should emit at least a warning when referencing sub-objects in the destructor, given the hidden danger of doing so. Although fundamentally, the real solution would be to fix the GC so that it can deal with full-featured destructors. Cheers, --AJG.
Aug 02 2005
Ohhhhh... I see what you mean now. So if you call explicit delete on the _super_ object, then its destructor can safely access sub-object references, is that correct?Exactly. Perhaps this would be a solution to your problem?
Aug 02 2005
"Walter" <newshound digitalmars.com> wrote in message news:dcn7ss$1drb$1 digitaldaemon.com...Generally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.This got me thinking about the uses of auto classes I've seen in D so far (note by auto objects I assume you mean an instance of an auto class). I've run into 4 auto classes: MmFile, ExeModule, PerformanceCounterScope (in phobos) and ScopedLock (in the locks library). The first two shouldn't be auto IMHO and MmFile is no longer auto. The last two are auto in order to ensure an action is performed at scope exit - either stop a performance counter or release a lock. Both of these auto classes can be replaced with the following auto class. By using this ScopeExit one can separate the concept of dtor called by the GC from the dtor called by exiting a scope. Also by reusing ScopeExit one can avoid ever having to define an auto class by hand. // the only auto class you'll ever need auto class ScopeExit { void delegate() dg; this(void delegate() dg) { this.dg = dg; } ~this(){ dg(); } } // example class Foo { void close() { printf("closing a Foo\n"); } } int main() { Foo f = new Foo; { auto ScopeExit exit = new ScopeExit(&f.close); printf("before exiting scope\n"); } printf("after exiting scope\n"); return 0; } Replacement code for the two auto classes PerformanceCounterScope and ScopedLock the code would look something like PerformanceCounterScope scope = new PerformanceCounterScope(counter); // original ScopeExit exit = new ScopeExit(&counter.stop); counter.start(); // new code ScopedLock scope = new ScopedLock(lock); // original code ScopeExit exit = new ScopeExit(&lock.unlock); lock.lock(); // new code
Aug 02 2005
In article <dcojar$2ggu$1 digitaldaemon.com>, Ben Hinkle says..."Walter" <newshound digitalmars.com> wrote in message news:dcn7ss$1drb$1 digitaldaemon.com...Neat! I like the idea. Cheers, HolgerGenerally speaking, destructors called by the garbage collector are not terribly useful, for the reasons you mentioned. Destructors called when they go out of scope (i.e. auto objects) are very useful.This got me thinking about the uses of auto classes I've seen in D so far (note by auto objects I assume you mean an instance of an auto class). I've run into 4 auto classes: MmFile, ExeModule, PerformanceCounterScope (in phobos) and ScopedLock (in the locks library). The first two shouldn't be auto IMHO and MmFile is no longer auto. The last two are auto in order to ensure an action is performed at scope exit - either stop a performance counter or release a lock. Both of these auto classes can be replaced with the following auto class. By using this ScopeExit one can separate the concept of dtor called by the GC from the dtor called by exiting a scope. Also by reusing ScopeExit one can avoid ever having to define an auto class by hand. // the only auto class you'll ever need auto class ScopeExit { void delegate() dg; this(void delegate() dg) { this.dg = dg; } ~this(){ dg(); } } // example class Foo { void close() { printf("closing a Foo\n"); } } int main() { Foo f = new Foo; { auto ScopeExit exit = new ScopeExit(&f.close); printf("before exiting scope\n"); } printf("after exiting scope\n"); return 0; } Replacement code for the two auto classes PerformanceCounterScope and ScopedLock the code would look something like PerformanceCounterScope scope = new PerformanceCounterScope(counter); // original ScopeExit exit = new ScopeExit(&counter.stop); counter.start(); // new code ScopedLock scope = new ScopedLock(lock); // original code ScopeExit exit = new ScopeExit(&lock.unlock); lock.lock(); // new code
Aug 02 2005