boost shared_ptr and parent child relation ships
Dear all, maybe this an off-topic issue, because it does not directly relate to shared_ptr and is more related to life time of objects and their validity during construction and destruction. There were the following issues: 1) childs validity depends on availability of parent and vise versa 2) in ctor one can not use the shared_from_this() 3) in destructor of the child the parent shared_ptr was inaccesible ad 1) the doc's are clear: you can not make a cycle in shared_ptrs between parent -> child and child -> parent. Thus now we use a weak_ptr for child -> parent, but actually the child can not exist without parent. So if one shares a child through a shared_ptr, a hypothetical situation could exist that its parent get destroyed, but a child lifes on (in an invalid state due to missing parent) because a client has requested it. ad 2) this is quite unhandy, since I thought it would be handy to let the parent create the childs passing itself. I think this can only be solved by two way construction. This may well turn out to be a fundamental problem. Consider two classes A and B which are associated with each other. Object of class A is valid when it is valid itself and when its associated object of class B is valid. When A gets changed it is temporalily invalid. It updates first B, but if B asks services from A during that call, it asks an invalid object. Reversing the order may help, but A may have to update itself again after it calls B. For two related classes it can be overseen, but of course real life applications have 1000 or more classes. Btw to some extend Bertrand Meyer's book touches this subject, but he only mentions the problem within a class (with public and private members). ad 3) the destructor of the child needed the parent to call another class. However the parent was inaccessible because it was destroyed itself. This is of course also a fundamental problem because when an object gets destroyed not all of its members may be valid (the parent object is in an invalid state), so calling the parent at that time was questionable too. The real problem was that it needed form the parent its parent which was still valid, solutions are obvious (but ugly) Sample code: struct Child; struct Parent; struct Child { Child(boost::shared_ptr<Parent> ptrParent) { m_ptrParent = ptrParent; } ~Child() { boost::shared_ptr<Parent> ptrParent = m_ptrParent.lock(); if (ptrParent) { //never come here } } boost::weak_ptr<Parent> m_ptrParent; }; struct Parent : public boost::enable_shared_from_this<Parent> { Parent() { //won't work: //m_ptrChild.reset(new Child(shared_from_this())); } boost::shared_ptr<Child> m_ptrChild; }; void TestParentChild() { boost::shared_ptr<Parent> ptrParent(new Parent); ptrParent->m_ptrChild.reset(new Child(ptrParent)); } wkr, me
gast128 wrote:
you can not make a cycle in shared_ptrs between parent -> child and child -> parent. Thus now we use a weak_ptr for child -> parent, but actually the child can not exist without parent. So if one shares a child through a shared_ptr, a hypothetical situation could exist that its parent get destroyed, but a child lifes on (in an invalid state due to missing parent) because a client has requested it.
Are consumers of this structure more likely to hold pointers to the parent or to a child? It sounds to me as though you want more direct control over the lifespans of parent and child. What if the parent's destructor were to delete all children outright? Then you'd never have orphans. You could hand out shared_ptrs to the parent object, so that it would persist as long as it had any consumers. As soon as the last consumer forgot about it, it would clean up all its own children. With that arrangement, you'd probably want direct consumers of the children to get weak_ptrs rather than shared_ptrs. That way a consumer could tell if the child had vanished. The parent should then hold the only shared_ptr to each child. (I'd suggest that the parent track its children with dumb child* pointers, but you need a shared_ptr on which to base your weak_ptrs.) And -- since you now know that the existence of a child implies the existence of its parent -- you could use dumb parent* pointers for the back-references from child to parent. That eliminates the need for the parent to obtain shared_from_this at constructor time. Does this address your constraints?
Are consumers of this structure more likely to hold pointers to the parent or to a child?
It sounds to me as though you want more direct control over the lifespans of parent and child. What if the parent's destructor were to delete all children outright? Then you'd never have orphans.
You could hand out shared_ptrs to the parent object, so that it would persist as long as it had any consumers. As soon as the last consumer forgot about it, it would clean up all its own children.
With that arrangement, you'd probably want direct consumers of the children to get weak_ptrs rather than shared_ptrs. That way a consumer could tell if the child had vanished.
The parent should then hold the only shared_ptr to each child. (I'd suggest that the parent track its children with dumb child* pointers, but you need a shared_ptr on which to base your weak_ptrs.)
And -- since you now know that the existence of a child implies the existence of its parent -- you could use dumb parent* pointers for the back-references from child to parent. That eliminates the need for the parent to obtain shared_from_this at constructor time.
Does this address your constraints?
This could help. However handing out a weak_ptr does not prevent situations that it is converted to a shared_ptr, while its parent is destroyed in the same calling sequence (later on). It sounds a little bit theoretical, but I had the hope (some day) that shared_ptr would be a replacement for raw pointers in almost every situation (preferably without thinking). I played a while ago with c# and its gc and I must admit that it works a little bit smoother than shared_ptr/weak_ptr's (dealing out raw pointers to all subsystems is like stoneage programming). Using gc has its own set of dynamics (non deterministic destruction, no use of references in finalizers), so you still have to think from time to time, but I got the impression that it would relieve a programmers job (for lets say 20-30%).
I already did a followup, but somehow it didn't come tru.
Are consumers of this structure more likely to hold pointers to the parent or to a child?
It sounds to me as though you want more direct control over the lifespans of parent and child. What if the parent's destructor were to delete all children outright? Then you'd never have orphans.
You could hand out shared_ptrs to the parent object, so that it would persist as long as it had any consumers. As soon as the last consumer forgot about it, it would clean up all its own children.
With that arrangement, you'd probably want direct consumers of the children to get weak_ptrs rather than shared_ptrs. That way a consumer could tell if the child had vanished.
The parent should then hold the only shared_ptr to each child. (I'd suggest that the parent track its children with dumb child* pointers, but you need a shared_ptr on which to base your weak_ptrs.)
And -- since you now know that the existence of a child implies the existence of its parent -- you could use dumb parent* pointers for the back-references from child to parent. That eliminates the need for the parent to obtain shared_from_this at constructor time.
Does this address your constraints? There are a number of solutions, like the one described above. But my hope was to replace the raw pointer with a shared_ptr/weak_ptr/scoped_ptr for almost any situation.
Dealing out a weak_ptr to a child, convert it to a shared_ptr when needed does not prevent the parent getting deleted in a next sequence of calls, leading to have a pointer to a child, without an exisitng parent. Other posts where about the need for calling the parent during destruction. Well there is one major case: if a parent is the observer of its child, the destructor of the child is the ideal candidate for unregistering. But in above case it leads to crashes. Although in this case the parent gets destroyed (in which the unregistering at the parent may be superfluous) there may be other observers for the child. All in all if I think about it, it can be solved (through a global register for instance), but not with above described proposal, which was attractive due to local character of the solution (child know only parents, maybe one class more). Then again it seems that c# and a gc wouldn't help in this case either. They break a cyclcic dependency, but it is forbidden to call object references in finalizers. Then again to get at that stage all observing objcts to the child had already give up their referneces, e.g soemthing like (in c++): Parent::~Parent() { std::for_each(m_vChilds.begin(), m_vChilds.begin(), boost::bind(&Parent::Notify, this, _1, eDelete); //type std::vector< boost::shared_ptr<Child> > m_vChilds.clear(); } etc. I know this sounds a little bit over the top, but we develop a pretty large application in our company, where you can not track all pointer of lifetime of object. Ideally a framework would solve lifetiem problems. Boost smart_ptr's help (and do a great job), but not completely.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Wednesday 25 June 2008 16:25 pm, gast128 wrote:
posts where about the need for calling the parent during destruction. Well there is one major case: if a parent is the observer of its child, the destructor of the child is the ideal candidate for unregistering. But in above case it leads to crashes. Although in this case the parent gets destroyed (in which the unregistering at the parent may be superfluous) there may be other observers for the child.
Signals/slots libraries (e.g. boost.signals) typically support automatic disconnection when either the observer or observed are destroyed. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQFIYq2W5vihyNWuA4URAtbIAJ4nyd7VO4yGkSJ45iKoPGfWjDTNmwCeJOxW CzYqo+6sX1FNHj88Repz/iE= =RDl7 -----END PGP SIGNATURE-----
Frank Mori Hess
Signals/slots libraries (e.g. boost.signals) typically support automatic disconnection when either the observer or observed are destroyed.
probalby solved by using a third object which can outlive the parent and the child, like I already said before. It is not that things are unsolvable, but that it was unexpected.
participants (3)
-
Frank Mori Hess
-
gast128
-
Nat Goodspeed