Thread overview | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
September 17, 2004 Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
After having explicitly deleted an object, the code below shows that it is still possible to call a member function of that object, albeit not directly. I would have expected a segmentation fault in stead. Sorry I could not reduce the code more. System: dmd 0.101 on Linux. Output: SpinBox 0x401cbfa0 constructed. Button constructed. Value in 0x401cbfa0: 1 SpinBox 0x401cbfa0 deleted. Value in 0x401cbfa0: 2 THE END. Button deleted. Signal deleted. Slot deleted. I would have expected a segfault after the "SpinBox deleted" line. #class Signal #{ # this(Button owner) # { # owner.register(this); # } # # ~this() # { # printf("Signal deleted.\n"); # } # # void connect(Slot s) # { # _slot = s; # } # # void emit() # { # _slot(); # } #private: # Slot _slot; #} # # #class Slot #{ # this(SpinBox owner, void delegate() callBack) # { # _callBack = callBack; # owner.register(this); # } # # ~this() # { # printf("Slot deleted.\n"); # } # # void opCall() # { # _callBack(); # } #private: # void delegate() _callBack; #} # #class Button #{ # Signal pressed; # # void press() # { # pressed.emit(); # } # # this() # { # pressed = new Signal(this); # printf("Button constructed.\n"); # } # # ~this() # { # printf("Button deleted.\n"); # } # # void register(Signal s) # { # _signals[s] = s; # } # #private: # Signal[Signal] _signals; #} # #class SpinBox #{ # void increase() # { # val++; # printf( "Value in %p: %d\n", this, val); # } # # Slot up; # # this() # { # printf("SpinBox %p constructed.\n", this); # up = new Slot(this, &increase); # } # # ~this() # { # printf("SpinBox %p deleted.\n", this); # } # # void register(Slot s) # { # _slots[s] = s; # } # #private: # Slot[Slot] _slots; # int val; #} # #void main() #{ # SpinBox moneyInTheBank = new SpinBox(); # Button incomeButton = new Button(); # # incomeButton.pressed.connect(moneyInTheBank.up); # incomeButton.press(); # # delete moneyInTheBank; # # incomeButton.press(); // Should produce a segfault. # # printf("THE END.\n"); #} |
September 17, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Bastiaan Veelo | Bastiaan Veelo wrote: > After having explicitly deleted an object, the code below shows that it is still possible to call a member function of that object, albeit not directly. I would have expected a segmentation fault in stead. Sorry I could not reduce the code more. <snip> IMO that's not a bug, it's a feature. Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes around. Secondly, GCs often keep ownership of memory they've collected, as it's more efficient than trading memory with the OS all the time. Hence your object reference still points into the GC heap, although the address is invalid and can be overwritten any time. To put it simply, delete has one purpose - to destruct an object when you're sure there's no chance of any potential ghosts to haunt the heap. Stewart. -- My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit. |
September 17, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stewart Gordon | Stewart Gordon wrote:
> Bastiaan Veelo wrote:
>
>> After having explicitly deleted an object, the code below shows that it is still possible to call a member function of that object, albeit not directly. I would have expected a segmentation fault in stead. Sorry I could not reduce the code more.
>
> <snip>
>
> IMO that's not a bug, it's a feature.
>
> Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes around.
I see. Dropping in a std.gc.fullCollect() right after the delete indeed changes the behaviour (Bus error).
I find it hard to get used to this GC stuff. In D, no references to other objects may occur in destructors. So you can not use a destructor to clean up things (like notifying other objects of our departure) the way you can in C++. Is a separate method like a discard() member function customary to clean things up in? The docs say that "The destructor is expected to release any resources held by the object." What resources would this be that you would not want to release in such a discard() method already? It seems to me that destructors have very limited use.
What does deleting an object impose other than calling the destructor? If there is no destructor, would deleting an object be equivalent to nulling all references to it?
This is maybe the wrong list for som many questions...
Thanks,
Bastiaan.
|
September 17, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Bastiaan Veelo | "Bastiaan Veelo" <Bastiaan.N.Veelo@ntnu.no> wrote in message news:cifg3v$2o49$1@digitaldaemon.com... > Stewart Gordon wrote: > > Bastiaan Veelo wrote: > > > >> After having explicitly deleted an object, the code below shows that it is still possible to call a member function of that object, albeit not directly. I would have expected a segmentation fault in stead. Sorry I could not reduce the code more. > > > > <snip> > > > > IMO that's not a bug, it's a feature. > > > > Firstly, AIUI delete doesn't deallocate the memory immediately - it just calls the destructor. The memory itself isn't freed until GC comes around. > > I see. Dropping in a std.gc.fullCollect() right after the delete indeed > changes the behaviour (Bus error). I wouldn't count on the object being valid (or invalid) after a delete call. The doc does say the memory is put on the free list but it looks like the current GC doesn't actually do that. I'm curious why you care what happens after a delete? > I find it hard to get used to this GC stuff. In D, no references to other objects may occur in destructors. So you can not use a destructor to clean up things (like notifying other objects of our departure) the way you can in C++. Is a separate method like a discard() member function customary to clean things up in? The docs say that "The destructor is expected to release any resources held by the object." What resources would this be that you would not want to release in such a discard() method already? It seems to me that destructors have very limited use. Agreed. It is very rare that destructors should be used. In particular there are questions about when/if a destructor gets run, from which thread it gets run and what else is alive when it gets run. Conceptually they are similar to C++ destructors since they both are about releasing resources but the "random" nature of D destructors makes them very different in practice. > What does deleting an object impose other than calling the destructor? I assume nothing else is guaranteed. > If there is no destructor, would deleting an object be equivalent to nulling all references to it? It looks like that is implementation-dependent. It could be that deleting an object with no destructor only releases the associated synchronization monitor (if any). > > > This is maybe the wrong list for som many questions... > > > Thanks, > Bastiaan. |
September 17, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | Ben Hinkle wrote:
> "Bastiaan Veelo" <Bastiaan.N.Veelo@ntnu.no> wrote in message
> news:cifg3v$2o49$1@digitaldaemon.com...
>
>>Stewart Gordon wrote:
>>
>>>Bastiaan Veelo wrote:
>>>
>>>
>>>>After having explicitly deleted an object, the code below shows that
>>>>it is still possible to call a member function of that object, albeit
>>>>not directly. I would have expected a segmentation fault in stead.
>>>>Sorry I could not reduce the code more.
>>>
>>><snip>
>>>
>>>IMO that's not a bug, it's a feature.
>>>
>>>Firstly, AIUI delete doesn't deallocate the memory immediately - it just
>>>calls the destructor. The memory itself isn't freed until GC comes
>
> around.
>
>>I see. Dropping in a std.gc.fullCollect() right after the delete indeed
>>changes the behaviour (Bus error).
>
>
> I wouldn't count on the object being valid (or invalid) after a delete call.
> The doc does say the memory is put on the free list but it looks like the
> current GC doesn't actually do that. I'm curious why you care what happens
> after a delete?
It was in testing my signal and slots mechanism (dcouple on dsource), in which cleaning up is one of the main focus points. I was curious as to what would happen if a user would delete an object containing signals and slots. I expected a crash, because there is no cleanup code in the destructor of that object, and was surprised that the object was still functioning afterwards.
It looks like a discard() method is necessary on objects containing signals and slots, which users must call when they have no use for an object anymore. In C++, they would delete the object at this time. This will be a confusing aspect of my library.
An alternative is to make objects that contain signals and/or slots (the owner) and the signals and slots themselves aware of each other's well-being, and have the destructor of the owner instruct its signals and slots /that are not deleted yet/ to disconnect. That way, deletion of the object automatically shuts it off as far as slots are concerned. I already have this functionality amongst signals and slots, but it does not include the owner. I was kind of hoping I could simplify and loose the reference to the owner in the signals and slots, but I think I am going to need it. It seems strange though that this extra data and functionality is required to make the object go away properly, and may not be needed during normal operation.
I always thought GC was going to make things easier --- I hope it will sometime.
Bastiaan.
|
September 20, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | In article <cifife$2s1u$1@digitaldaemon.com>, Ben Hinkle says... <snip> > Agreed. It is very rare that destructors should be used. In particular there are questions about when/if a destructor gets run, from which thread it gets run and what else is alive when it gets run. But I've seen it said that you can override the question about if by calling gc_term when your program exits.... > Conceptually they are similar to C++ destructors since they both are about releasing resources but the "random" nature of D destructors makes them very different in practice. <snip> I've had no problems simply using destructors to free resources. However, when one destructor wants to call another, things get difficult, and the question of what else is alive is indeed a question. I'm working on a tree control implementation for SDWF 0.4. AIUI, you don't need to free the handles of individual tree items, but the Item objects still present a challenge. This is because the TreeView class needs to be able to get the current selection. To do this, it keeps an AA mapping handles to Item objects. When an item is removed, it removes it from this AA, but there are also the item's child items to remove. So the Item destructor calls its child items' destructors. (This feature is as yet untested.) The issue is ensuring that an object isn't destructed twice (or more). This is needed anyway, otherwise everything that's been explicitly deleted would be destructed again when the GC discovers it's unreachable. This is actually a straightforward issue to address - either every object's isDestructed property needs to be accessible, or (even better) delete would automagically check. Stewart. |
September 20, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stewart Gordon | "Stewart Gordon" <Stewart_member@pathlink.com> wrote in message news:cimoal$2nor$1@digitaldaemon.com... > In article <cifife$2s1u$1@digitaldaemon.com>, Ben Hinkle says... <snip> > > Agreed. It is very rare that destructors should be used. In particular there are questions about when/if a destructor gets run, from which thread it gets run and what else is alive when it gets run. > > But I've seen it said that you can override the question about if by calling gc_term when your program exits.... gc_term is already called at program exit. The garbage collector could even then mistakely believe an object has a live reference to it so it is possible to never actually collect a given object. The probability is very low (I assume) but it is there. Of course most system resources are cleaned up by the OS at program exit anyway so it shouldn't really matter. > > Conceptually they are similar to C++ destructors since they both are about releasing resources but the "random" nature of D destructors makes them very different in practice. > <snip> > > I've had no problems simply using destructors to free resources. system resources should be fine. GC managed resources should be avoided with a ten foot pole. > However, when one destructor wants to call another, things get difficult, and the question of what else is alive is indeed a question. > > I'm working on a tree control implementation for SDWF 0.4. AIUI, you don't need to free the handles of individual tree items, but the Item objects still present a challenge. > > This is because the TreeView class needs to be able to get the current selection. To do this, it keeps an AA mapping handles to Item objects. ok > When an item is removed, it removes it from this AA, > but there are also the item's child items to remove. So the Item > destructor calls its child items' destructors. (This feature is as > yet untested.) What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC. If it by using a member functions like remove() then I'd have the parent's remove function call remove on the children. > The issue is ensuring that an object isn't destructed twice (or more). This is needed anyway, otherwise everything that's been explicitly deleted would be destructed again when the GC discovers it's unreachable. The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though. > This is actually a straightforward issue to address - either every object's isDestructed property needs to be accessible, or (even better) delete would automagically check. > > Stewart. > > |
September 20, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Bastiaan Veelo | "Bastiaan Veelo" <Bastiaan.N.Veelo@ntnu.no> wrote in message news:ciftct$dft$1@digitaldaemon.com... > Ben Hinkle wrote: > > "Bastiaan Veelo" <Bastiaan.N.Veelo@ntnu.no> wrote in message news:cifg3v$2o49$1@digitaldaemon.com... > > > >>Stewart Gordon wrote: > >> > >>>Bastiaan Veelo wrote: > >>> > >>> > >>>>After having explicitly deleted an object, the code below shows that it is still possible to call a member function of that object, albeit not directly. I would have expected a segmentation fault in stead. Sorry I could not reduce the code more. > >>> > >>><snip> > >>> > >>>IMO that's not a bug, it's a feature. > >>> > >>>Firstly, AIUI delete doesn't deallocate the memory immediately - it just > >>>calls the destructor. The memory itself isn't freed until GC comes > > > > around. > > > >>I see. Dropping in a std.gc.fullCollect() right after the delete indeed > >>changes the behaviour (Bus error). > > > > > > I wouldn't count on the object being valid (or invalid) after a delete call. > > The doc does say the memory is put on the free list but it looks like the > > current GC doesn't actually do that. I'm curious why you care what happens > > after a delete? > > It was in testing my signal and slots mechanism (dcouple on dsource), in which cleaning up is one of the main focus points. I was curious as to what would happen if a user would delete an object containing signals and slots. I expected a crash, because there is no cleanup code in the destructor of that object, and was surprised that the object was still functioning afterwards. > > It looks like a discard() method is necessary on objects containing signals and slots, which users must call when they have no use for an object anymore. In C++, they would delete the object at this time. This will be a confusing aspect of my library. Why is that confusing? Writing delete foo; is not much different than foo.disconnect(); or foo.discard(); or whatever the API happens to be. > An alternative is to make objects that contain signals and/or slots (the owner) and the signals and slots themselves aware of each other's well-being, and have the destructor of the owner instruct its signals and slots /that are not deleted yet/ to disconnect. That way, deletion of the object automatically shuts it off as far as slots are concerned. I already have this functionality amongst signals and slots, but it does not include the owner. I was kind of hoping I could simplify and loose the reference to the owner in the signals and slots, but I think I am going to need it. It seems strange though that this extra data and functionality is required to make the object go away properly, and may not be needed during normal operation. I looked at the module dcouple.signal on dsource and noticed that SignalGenericCore's destructor calls disconnect() which loops over the slots in _slots and disconnects each one. I would be careful about looping over _slots from a destructor since _slots might already have been cleaned up - or partially cleaned up. > I always thought GC was going to make things easier --- I hope it will sometime. The GC makes memory management easier but should not be used to manage relationships between objects - or at least one should be very careful when trying to manage relationships using the GC. > > Bastiaan. |
September 20, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ben Hinkle | In article <cimpvb$2oog$1@digitaldaemon.com>, Ben Hinkle says... > "Stewart Gordon" <Stewart_member@pathlink.com> wrote in message news:cimoal$2nor$1@digitaldaemon.com... <snip> > gc_term is already called at program exit. Unless the compiler has changed since last time, then not according to what I was told. Hence its presence in the boilerplate WinMain function is extra to D's standard behaviour. > The garbage collector could even then mistakely believe an object has a live reference to it so it is possible to never actually collect a given object. The probability is very low (I assume) but it is there. Of course most system resources are cleaned up by the OS at program exit anyway so it shouldn't really matter. Yes, _most_ system resources. But not, for example, file objects with write caching. And even with those that are, I guess some people like to be on the safe side anyway.... <snip> >> When an item is removed, it removes it from this AA, but there are also the item's child items to remove. So the Item destructor calls its child items' destructors. (This feature is as yet untested.) > > What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC. It has a remove method, which calls the WinAPI to remove the item from the tree and then deletes this. > If it by using a member functions like remove() then I'd have the parent's remove function call remove on the children. That would cause them to be separately removed from the tree. If done after the parent is removed, it would be an invalid WinAPI call; if done before, it would work but might be more time-consuming if there's lots to remove. Of course, another possibility is to have remove call some internal method (other than the destructor) to remove stuff from the index.... >> The issue is ensuring that an object isn't destructed twice (or more). This is needed anyway, otherwise everything that's been explicitly deleted would be destructed again when the GC discovers it's unreachable. > > The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though. <snip> I mean, if you have something like Qwert yuiop = new Qwert; delete yuiop; delete yuiop; will there necessarily be only one destructor call? Stewart. |
September 20, 2004 Re: Destructed object is still alive. | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stewart Gordon | Stewart Gordon wrote: > In article <cimpvb$2oog$1@digitaldaemon.com>, Ben Hinkle says... > >> "Stewart Gordon" <Stewart_member@pathlink.com> wrote in message news:cimoal$2nor$1@digitaldaemon.com... > <snip> >> gc_term is already called at program exit. > > Unless the compiler has changed since last time, then not according to what I was told. Hence its presence in the boilerplate WinMain function is extra to D's standard behaviour. ah - I see what you mean. I meant that it is called by the standard D startup/shutdown code in src/phobos/internal/dmain2.d >> The garbage collector could even then mistakely believe an object has a live reference to it so it is possible to never actually collect a given object. The probability is very low (I assume) but it is there. Of course most system resources are cleaned up by the OS at program exit anyway so it shouldn't really matter. > > Yes, _most_ system resources. But not, for example, file objects with write caching. And even with those that are, I guess some people like to be on the safe side anyway.... agreed, I think. > <snip> >>> When an item is removed, it removes it from this AA, but there are also the item's child items to remove. So the Item destructor calls its child items' destructors. (This feature is as yet untested.) >> >> What is the API to remove an item? If it is by calling delete you will have a problem since the AA is managed by the GC. > > It has a remove method, which calls the WinAPI to remove the item from the tree and then deletes this. ok. though I wonder what deleting this does. Is it needed? Why not just have remove call the Windows API to remove the item? >> If it by using a member functions like remove() then I'd have the parent's remove function call remove on the children. > > That would cause them to be separately removed from the tree. If done after the parent is removed, it would be an invalid WinAPI call; if done before, it would work but might be more time-consuming if there's lots to remove. > > Of course, another possibility is to have remove call some internal method (other than the destructor) to remove stuff from the index.... I'm a little lost without more context so I'll try getting your library and poking around sometime. I can see your point about worrying about efficiency and also trying to use destructors to guarantee removal. >>> The issue is ensuring that an object isn't destructed twice (or more). This is needed anyway, otherwise everything that's been explicitly deleted would be destructed again when the GC discovers it's unreachable. >> >> The GC already guarantees an object's destructor won't be called twice. I'm not quite sure if this is what you mean, though. > <snip> > > I mean, if you have something like > > Qwert yuiop = new Qwert; > delete yuiop; > delete yuiop; > > will there necessarily be only one destructor call? I'm not sure - or maybe the answer is "it depends". There might be a problem if another thread jumps in between the two delete calls and allocates a new object right exactly where yuiop is pointing and then the second delete would actually end up deleting that new object (ick!). I don't really know the GC well enough to know if this is possible but off the top of my head I would assume it could happen. If such threading behavior can't happen in your app then yes the destructor is only called once because the first delete clears a flag that the GC uses to keep track of what needs destroying. > > Stewart. |
Copyright © 1999-2021 by the D Language Foundation