Skip to main content
gamedev.net
🔒 Locked

Smart Pointer Dilema

Started by Raeldor Oct 9, 2006 at 9:15 PM 23 replies 3.6k views
Original Post
Raeldor
Raeldor
Hi All, I have a bit of a dilema with smart pointers. I have a GUI object that has an m_children property which is an array of smart pointers to the windows. When I create the window, the window constructor adds itself to the list of child windows for the gui. Problem now is that if I then set my window variable to NULL to destroy the window it doesn't get destroyed because it's still referenced in the list of children. Should the child list not use smart pointers? Seems a little unsafe to have a non-smart-pointer to a smart object. What are my other options here? I assume this is quite a common problem. Thanks! Rael
dave
dave
This is a tricky one because you can't really do anything clever in the destructor, like have it remove itself from its parents children, because it could throw an exception and it's just nasty.

Off the top of my head, one way you could do it is to have a member function on each control that alters a member bool that indicates whether the control is alive or not.

So you could go through and set any children that you want to destroy to not alive and then run some sort of sequence of propogation from the top parent control down to ask each parent to remove any dead children.

Dave
Raeldor
Raeldor
Quote:
Original post by Dave
This is a tricky one because you can't really do anything clever in the destructor, like have it remove itself from its parents children, because it could throw an exception and it's just nasty.

Off the top of my head, one way you could do it is to have a member function on each control that alters a member bool that indicates whether the control is alive or not.

So you could go through and set any children that you want to destroy to not alive and then run some sort of sequence of propogation from the top parent control down to ask each parent to remove any dead children.

Dave

Quite so. I was planning to do just that... remove it in the destructor, but I guess that's not happening anymore.

This also seems to generally be a problem with a parent/child relationship and smart pointers in general. I mean if you have a pointer in the parent to the children and a pointer in the child to the parent how is either of them ever going to get destroyed?
TheC00L1
TheC00L1
I'm not sure if this helps or not, so don't curse me if it doesn't, anyway...

i usually, in those relationships, have the parent destroy the child, so the child's pointer to the the parent doesnt exists, and then when the parent destroys the child. The parent's pointer to the child is set to NULL.

If not that, what you could do is delete an child from the array, rather than the child delete itself.
A wiseman once said nothing, but no one was there to hear it.
ToohrVyk
ToohrVyk
Quote:
Original post by Raeldor
This also seems to generally be a problem with a parent/child relationship and smart pointers in general. I mean if you have a pointer in the parent to the children and a pointer in the child to the parent how is either of them ever going to get destroyed?


This is a problem with reference-counting pointers. Smart pointers in general have no reason to dislike cyclical data structures. See, for instance, boost::weak_ptr.
Raeldor
Raeldor
Quote:
Original post by ToohrVyk
Quote:
Original post by Raeldor
This also seems to generally be a problem with a parent/child relationship and smart pointers in general. I mean if you have a pointer in the parent to the children and a pointer in the child to the parent how is either of them ever going to get destroyed?


This is a problem with reference-counting pointers. Smart pointers in general have no reason to dislike cyclical data structures. See, for instance, boost::weak_ptr.

I am not familiar with the boost library. Are you saying that the boost smart pointer does not use reference counting? How does it work?
Thanks
joanusdmentia
joanusdmentia
boost::shared_ptr uses reference counting, boost::weak_ptr is 'connected' to a shared_ptr, but doesn't affect the reference count. In order to use an object pointed to by a weak_ptr, you must first obtain a shared_ptr to the object. If the object pointed to by the weak_ptr is destroyed, the weak_ptr will become 'expired'.

// Create the integer, refcount=1boost::shared_ptr<int> a(new int(0));// Obtain a weak_ptr to the integer, refcount=1// (b cannot be used to access the pointer directly)boost::weak_ptr<int> b(a);// Obtain another shared_ptr to the integer, refcount=2boost::shared_ptr<int> c = b.lock();// Release the first shared_ptr, refcount=1// (integer isn't destroyed because of c)a.reset();// Does the pointer still exists? (it does)if(b.expired()) std::cout << "pointer destroyed";else std::cout << "pointer still OK";// Release the second shared_ptr, refcount=0// (integer is destroyed, despite the weak_ptr)c.reset();// Does the pointer still exists? (it doesn't)if(b.expired()) std::cout << "pointer destroyed";else std::cout << "pointer still OK";
"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The nly verdict is vengeance; a vendett o
jochen
jochen
Hi Raeldor,

joanusdmentia explained completely right. You should be able to solve your problem by defining ownerschip relations. boost::shared_ptr is an owning ptr. boost::weak_ptr is not. So if the parent owns its children and the children only keep a weak reference to the parent, u should be o.k.

Additionally I'd like to add a note about boost smart pointers. Boost smart pointers are pretty much what will be in the C++ standard, soon. So imho its a good thing to go for boost smart pointers.

I think (or hope) there will be a parallel to "done it myself" container implementations... there are no more. Every serious developer will tell you to use stl containers intead, at least that's what I do. ;-)

Right now, probably every serious developer will tell you to use boost smart pointers. After the release of C++0x everybody will tell you to use stl smart pointers, which will effectively be the same.

Jochen
Why did Doug Gregor create the empty directory?
Raeldor
Raeldor
Wow, this is very useful, thanks guys. Makes a lot of sense, I'll have to go re-visit my code now with the 'ownership' in mind.

Thank you!
Raeldor
Raeldor
One more question. Does anyone know how these weak pointers work internally? In the example above, when c.reset() is called then the object is destroyed so how is the b.expired() function able to tell that the referenced smart pointer no longer exists if the memory is invalid?

Thanks!
NotAYakk
NotAYakk
This may not be how boost weak pointers work, but here is a way it can work.

Use the listener/observer pattern. The weak pointers listen for the destruction of their associated strong smart pointers -- and when it happens, they sever their relationship and record themselves as expired.

Alternatively, many smart pointer implementations allocate a second "counting" block of memory. That is where the reference count is stored (you can't store it in the smart pointers, because they are on the stack, and can go away). Modify that block of memory so that is has more than one count: the "strong" count and the "weak" count.

If the "strong" count is 0, the weak pointer is expired. But the "counting" memory doesn't go away until the weak pointer count goes to 0.

(The details are tricky, and not for the faint of heart to implement -- the weak pointer, when it wants to spawn a strong pointer, would have to use a CAS instruction (NOT an atomic increment) to increment the strong pointer count -- and refuse to increment the count if the strong pointer count is 0).

Note that the listener/observer pattern implementation is equally hairy to deal with -- if not more so!

Are you planning on using boost, or implementing your own weak pointers?
Grain
Grain
Quote:
Original post by NotAYakk
This may not be how boost weak pointers work, but here is a way it can work.

Use the listener/observer pattern. The weak pointers listen for the destruction of their associated strong smart pointers -- and when it happens, they sever their relationship and record themselves as expired.

Alternatively, many smart pointer implementations allocate a second "counting" block of memory. That is where the reference count is stored (you can't store it in the smart pointers, because they are on the stack, and can go away). Modify that block of memory so that is has more than one count: the "strong" count and the "weak" count.

If the "strong" count is 0, the weak pointer is expired. But the "counting" memory doesn't go away until the weak pointer count goes to 0.

(The details are tricky, and not for the faint of heart to implement -- the weak pointer, when it wants to spawn a strong pointer, would have to use a CAS instruction (NOT an atomic increment) to increment the strong pointer count -- and refuse to increment the count if the strong pointer count is 0).

Note that the listener/observer pattern implementation is equally hairy to deal with -- if not more so!

Are you planning on using boost, or implementing your own weak pointers?

Couldn't there be a hash map as a static member of the smart pointer class where smart pointers register them selves and their associated memory?
jpetrie
jpetrie
That wouldn't gain you anything (you still have the same kinds of issues to solve), but it would make implementation more complex and less efficient. Smart pointers are already hard enough to get right.
Raeldor
Raeldor
What about the weak pointers registering themselves with the object that belongs to the smart pointer. That way when a smart pointer finally destroys the object, it can set all the weak pointer expired flags to true?
Fruny
Fruny
Quote:
Original post by Raeldor
What about the weak pointers registering themselves with the object that belongs to the smart pointer.


I've had a quick look over Boost's weak pointer code and here is my understanding of it:

Shared pointers use a separate object (of type boost::detail::sp_counter_base) that keeps track of the reference count to the pointee. That object keeps track of both the number of shared pointers and the number of weak pointers. When the number of shared pointers falls to zero, the pointee gets destroyed.

From then on, weak pointers, which also use that same count object, will report themselves as expired and refuse locking. Only when the weak pointer count also falls to zero will the count object be destroyed.

This is preferable to keeping a list of weak pointers and actively resetting them since it only ever requires a constant amount of memory, rather than additional memory for each and every weak pointer, and doesn't require any extra work to be performed when the pointee is destroyed. Rather than having to be actively marked expired, they simply report that they are pointing to a dead object. Simple as that.

[Edited by - Fruny on October 10, 2006 4:11:09 PM]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Raeldor
Raeldor
Quote:
Original post by Fruny
Quote:
Original post by Raeldor
What about the weak pointers registering themselves with the object that belongs to the smart pointer.


I've had a quick look over the weak pointer code and here is my understanding of it:

Boost's shared pointers use an object to keep track of the reference count. That object really keeps track of two reference counts: the shared reference count, and the weak reference count. Both shared and weak pointer reference that object (which is why you need a shared pointer to create a weak pointer).

When the shared count hits zero, the pointee is destroyed as usual. From now on, weak pointers will report themselves as expired. Only when both the shared and the weak reference counts reach zero will the reference counter itself be destroyed.

I agree with their trade-off. It is a much better solution than keeping a list of all the weak pointers and actively marking them as expired when time comes.

Interesting, so the 'object' that is used to keep track of the reference count is seperate to the object than the one you are smart pointing to?

I always assumed that all classes that can be smart pointer'd are sub-classed from that top level 'object'. If they are sub-classed, then the implementation you talk about does not seem to solve the parent/child circular reference problem because the smart pointer'd objects destructor still doesn't get called when there are weak pointers.

Thanks
joanusdmentia
joanusdmentia
I just thought I'd also throw in the boost's implementation (and therefore the Standard C++ Library's, once the next revision is finalised) is thread-safe. You can safely have multiple shared_ptr's and weak_ptr's in different threads pointing to the same object. What you can't do though is use the *same* shared_ptr or weak_ptr in different threads at the same time.

For example:

class MyThingy {};boost::shared_ptr<MyThingy> mythingy;void MyThread1(boost::weak_ptr<MyThingy> ptr){    // This is OK, points to same object as mythingy    boost::shared_ptr<MyThingy> a = ptr.lock();    // This is BAD, MyThread2 may be in    // the middle of copying mythingy    mythingy.reset();}void MyThread2(boost::weak_ptr<MyThingy> ptr){    // This is OK, points to same object as mythingy,    // or is empty if MyThread1 has already completed    boost::shared_ptr<MyThingy> a = ptr.lock();    // This is BAD, MyThread1 may be in    // the middle of resetting mythingy    boost::shared_ptr<MyThingy> b = mythingy;}void StartThreads(){    mythingy.reset(new MyThingy());    StartThread(MyThread1, mythingy);    StartThread(MyThread2, mythingy);}
"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The nly verdict is vengeance; a vendett o
joanusdmentia
joanusdmentia
Quote:
Original post by Raeldor
Interesting, so the 'object' that is used to keep track of the reference count is seperate to the object than the one you are smart pointing to?

I always assumed that all classes that can be smart pointer'd are sub-classed from that top level 'object'. If they are sub-classed, then the implementation you talk about does not seem to solve the parent/child circular reference problem because the smart pointer'd objects destructor still doesn't get called when there are weak pointers.

Thanks


The boost smart pointers (there's more than just shared_ptr and weak_ptr) can point to any type of object. The shared_ptr/weak_ptr combo does solve the circular reference problem you're having, the pointed-to object is destroyed when the shared_ptr reference count reaches 0 regardless of how many weak_ptr's point to that object.

Each shared_ptr and weak_ptr actually contains 2 pointers, a pointer to the object and a pointer to the reference count. The reference count actually contains 2 counts, the number of shared_ptr's and the number of weak_ptr's that reference the object. The instant that the shared count reaches 0, the pointed-to object is destroyed, and from this point onwards all the weak_ptr objects are expired and you can never create another shared_ptr to the original object from a weak_ptr (which is good, since the object has been destroyed [smile]). Once the weak count reaches 0, the pointer to the reference count is destroyed.
"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The nly verdict is vengeance; a vendett o
Fruny
Fruny
Quote:
Original post by Raeldor
Interesting, so the 'object' that is used to keep track of the reference count is seperate to the object than the one you are smart pointing to?


Yes. Boost's smart pointers are non-intrusive... except for boost::intrusive_ptr, which assumes there exist intrusive_ptr_add_ref and intrusive_ptr_release functions that accept pointers of the appropriate type.

Quote:
I always assumed that all classes that can be smart pointer'd are sub-classed from that top level 'object'.


This is C++, not Java. There is no top-level object class. That doesn't stop you from creating smart pointers to a plain old int.

Quote:
If they are sub-classed, then the implementation you talk about does not seem to solve the parent/child circular reference problem because the smart pointer'd objects destructor still doesn't get called when there are weak pointers.


Here is the approximate architecture. Boost's is a bit different but that doesn't invalidate the whole idea.

                       *************Shared pointer  -----> * Reference * ----> PointeeWeak   pointer  -----> *  Counter  *                       *************


When the count for shared pointers falls to zero, the pointee is destroyed.
When the counts for both shared and weak pointers fall to zero, the counter is destroyed.
If you try to use a weak pointer when the shared pointer count is zero, it will report that the object is dead.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Grain
Grain
How does a newly created shared pointer know if they are other shared pointers to an object? All the information its given is the objects address? I couldn't find any thing in the boost files where it looks up if that address is stored in the shared pointers constructors.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.