|
Boost : |
Subject: Re: [boost] [paired ptr] Proposing a new smart pointer object for managing bidirectional relationships between objects
From: Dan Walters (dan683_at_[hidden])
Date: 2010-05-06 07:05:15
Hi all,
Thank you Rob and Vicente again for your advice and encouragement.
I will again try to respond to all at once.
Vicente:
> I think this is what Rob is saying. You need just to define the prototype for the callback. If the user needs other arguments bind will help.
What I have ended up doing is this:
// connect_callback is a boost::function1<void,paired_ptr<T,U>*>
template<class T1>
void set_connect_callback(T1* p_object, void
(T1::*member_function)(paired_ptr<T,U>*))
{
connect_callback = std::bind1st(std::mem_fun(member_function), p_object);
}
There is also a non member version which assigns the non member
function pointer directly to the boost::function1.
So paired_ptr now supports:
non member functions
static member functions
member functions // of any type
> Nothing forbids to implement a has relationship using inheritance of a relation. Alexandrescu showed in its seminal book Modern C++ some techniques using inheritance to implement members.
Looking at your examples, I can see why this now makes sense.
To implement this inherited version, it would need to be a separate
class, and internally would need to work slightly differently.
This is a less favorite implementation of mine as (while being much
tidier to code with), it is harder to understand. The benefit of
paired_ptr as a member, is that all programmers are familiar with the
concept of a class member, and pointers. paired_ptr is used a little
like a pointer.
The limitations of the inherited version is that you can only have one
paired_ptr to each set of types, and any user has to be more familiar
with templates to be able to use the class. A member version can also
be dynamically contained within containers, swapped with other
objects, etc.
Right now, despite your convincing argument, I am on the side of
keeping it simple and easier to get to grips with, remaining with the
non-inherited version. I could be convinced otherwise should there be
enough support that the option is valuable.
> Not realy a dynamic_cast, just an overloading. Of course when tag are used, tags must be used. p.get<father>();
I understand how this works now. Again, very very tidy interface. For
me, the only drawback is that its going to need either more time with
documentation or a more skilled user to get started with the library.
>The left and right in bimap is only a way to name the two ends. It could be first and second also.
The advantage of bimap is that it allows to iterate on the association
and that the end classes are independent. I'm not saying yours is not
useful. And yes, paired_ptr is much much simpler.
>I use a similar class to define roles a class can play. The difference is that a role needs always to be associated to an entity, so the association is created on role constructor and destroyed on role destruction or role migration. The entity objects allow to get the roles it plays and the role objects provide the same interface than the entity, including getting other roles of the entity.
As opposed to your roles class, paired_ptr can be empty, as I
understand. Should paired_ptr be a complete library, would it be able
to solve the role-entity relationship sufficiently?
>I'm not sure this smart pointer should be included in the Boost.SmartPtr
>library. There are a lot of smart pointers to put all of them in
>Boost.SmartPtr. I would put it is a specific library. Intrusive
>Associations.
Agreed, the library is a type of smart pointer, but its selling point
is that it is an association.
>to what constructors and operators are you referring to?
assignment, comparison, etc. I have barely started container support
but they do not yet go in containers. I need to do more work before I
can discuss this further really.
>I agree that the callback version must be separated from the simpler one.
>Why do you need the owner? in which cases it is needed?
I need the owner as the relationship is as follows:
T1 objectA <----> paired_ptr<T1,T2> <----> paired_ptr<T2,T1> <----> T2 objectB
to be able to get objectB from objectA, you must trace along the
complete line. Each object must be bidirectional to the following one.
So, when objectA wants a pointer to objectB, it accesses its own
paired_ptr, that retrieves the other paired_ptr, and gets the owner of
that paired_ptr.
A modification is that the local paired_ptr could contain the target
object rather than the owner object. This would mean that connectiion
rather than being
paired_ptr<T,U>::connect(paired_ptr<U,T>*)
you would now need
paired_ptr<T,U>::connect(U*, paired_ptr<U,T>*)
as every time you connect to a different target, you would need the
pointer to that target object, but also the poointer to the paired_ptr
object to manage that relationship.
A more complex connect operation but no owner initialization
function...... which to choose?
> No I don't thisnk so, as this is already stored on the pointer.
> Could you clarify your use case?
If an object contains a std::container of type
paired_ptr<person,person>, then the user is going to have a lot of
paired_ptrs all calling back to the same callback functions. By
providing the paired_ptr*, the function is now aware of which
paired_ptr in the container has just connected / disconnected.
>>
>> 5. polymorphic usage. You should be able to do paired_ptr<CDog, CAnimal>
>> ptr_A; paired_ptr<CDog, CCat> ptr_B; ptr_A = ptr_B;
>>
>Yes this is a must have.
This is supported. Note the following will not work.
//class boy : public person
paired_ptr<boy,person> paired1, paired2;
paired1.connect(&paired2); // error: cannot convert
paired_ptr<boy,person> to paired_ptr<person,boy>
this tries to morph person into a boy.
Instead you have to use:
paired_ptr<person,person> paired1, paired2;
Rob:
>> For MSVC, warning 4355 is disabled in the paired_ptr header.
>Did you use #pragma warning (push) and #pragma warning (pop) to control its scope?
no, i just disabled. Note that the error is generated in the user
defined class and not in the paired_ptr source.
>As I said before, do not assume that the object type for the member function pointer is the owner type. A user may wish to invoke a member function on a different object.
callback now supports member functions from any class using a
templated function.
> The argument type should be a reference since you'll ensure it is never null. Don't make the library user check that for every callback.
You are absolutely right, I will go through the source and make sure I
am using references in the correct places.
>....
>You should support this overload, too:
>....
All solid points, I have now implemented. I am also using boost::bind
rather than bind1st.
>Of course, but that's why I pointed out that you'd use type erasure via Boost.Function and Boost.Bind (or std::bind1st).
This i somehow missed. Im on it now, and have made changes.
>You don't need to leave out non-static member functions with the approach I suggested.
Did take me a while to get my head around how this works, but all sorted now.
>pairing_ptr?
>linking_ptr?
>connection?
>link?
>association?
pairing is much better than paired as it suggests the operation rather
than a state.
I think ptr in the name is important as it helps the user understand
quickly how the object may be used. connection and link do not really
suggest the specifics.
bidirectional_ptr or bi_ptr is probably the best name I can come up
with at the moment. The object clearly is an association but that word
doesn't really suggest the one to one relationship. As you say,
suggesting n-ary support.
>I think it is quite reasonable as a smart pointer. There are many uses for that class (no pun intended) of thing.
After further thought, I agree. This class is much closer to a pointer
than anything else.
>Both arguments have merit. There are other approaches for one-to-many and many-to-many relationships. However, as you pointed out above, one of the distinguishing characteristics of your class is that it does no memory management. It also differs in that it provides callbacks to indicate when associations are formed and severed. Your class manages the associations and not the objects. Consequently, were that extended to one-to-many and many-to-many associations, it would remain distinct from other solutions.
Very well argued. A one to many association could work where each
paired_ptr connects to, rather than a single other paired_ptr, a
container of objects. This container would provide callback
functionality to all elements, and each element would be responsible
for managing the container.
Note with this design, the one to many relationship would have to be
of a single type. The container can only hold a single type of object.
This looses the powerful interface offered in paired_ptr where you can
connect any type to any other type.
With this considered, why would the one-to-many version be any
different from any std::container<type*> ? Just the callback
functionality allowing objects to share management of the association?
Vicente:
>With the new callback prototype you don't need any more the owner. So if
>you make to independent classes, without call back only a pointer is needed.
As i explain above, a paired_ptr must be aware of its owner so that
the other paired_ptr can access its target object rather than just the
paired_ptr member of that object.
Vicente and Rob, thank you again for the great feedback and
challenging conversation.
I am currently working towards a new source release and updated /
re-written documentation. In the mean time, please feel free to give
any comments :D They are greatly appreciated.
Best wishes,
Dan
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk