Boost logo

Boost :

Subject: Re: [boost] [paired ptr] Proposing a new smart pointer object formanaging bidirectional relationships between objects
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-05-06 20:24:04


----- Original Message -----
From: "Dan Walters" <dan683_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Thursday, May 06, 2010 1:05 PM
Subject: Re: [boost] [paired ptr] Proposing a new smart pointer object formanaging bidirectional relationships between objects

>
> 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

Yes. This should work and some users will appreciate that you hide the use of bind, but this is not absolutely necesary.
 
>> 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,

With the use of tagged types you can has as many as you want. tagged<person, child>, tagged<person, father>, ..;

> 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.

OK. I see the problem.

> 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?

I think that I have an idea. Currently when you connect the user does the following

t->end1.connect(u->end2);

It has all the informations. The idea is

Suppose that paired_ptr<T, U> stores a pointer to U and that connect takes two tagged objects.

template <typename TTag, typename UTag, typename T, typename U>
void connect<father, child>(T*,U*);

We can define a meta-function that giving a pointer to a class U and the tagged type tagged<Tag, T> retrieves the paired_ptr<U, tagged<T, Tag> >.

template <typename T, typename Tag, typename U>
struct get_paired_ptr
//{
// static paired_ptr<U, tagged<T, Tag> >& apply(U*);
//};

This metafunction must be specialized for each paired_ptr member.

template <>
struct get_paired_ptr<person, child, person> {
  static paired_ptr<person, tagged<person,child> >& apply(person* p) {
    return p->child_;
  }
};

If you think that the user could find this hard to write you can provide a macro that do this.

BOOST_PAIRED_PTR_GET_DCL(T, TAG, U, FIELD);
BOOST_PAIRED_PTR_GET_DCL(child, person, person, child_);

The function connect can be implemented as follows

template <typename TTag, typename UTag, typename T, typename U>
void connect<father, child>(T* t,U*u) {
    paired_ptr<T, tagged<U,UTag> > *ptrU = get_paired_ptr<T, tagged<U, UTag> >::apply(t);
    paired_ptr<U, tagged<T,TTag> > *ptrT = get_paired_ptr<U, tagged<T, TTag> >::apply(u);
   // as before
   // ...
}
 
The function get_paired_ptr can have as default behavior when the class T inherits from paired_ptr<T, tagged<U,UTag> >.

If we are able to remove the owner, then the class doesn't need anymore two template classes. That is, paired_ptr will have only the pointee type. All the previous design will be a little bit more simple.

I understand that you can find this too complex, but at the end the user needs to make not too many things, with the advantage that the paired_ptr takes only the size of a pointer. Another advangae is that this is more efficient as we have directly the poiner to the object avoinding a double indirection.

I think that for the callbacks, it would be worth to try to have them statically. Maybe a system like the get_paired_ptr could work also.

With the tags, we are doing some kind of reflexion, that allow us to get static information using metaprogramming, as the associated field and the callbacks.
 
>> 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;

I would use instead
paired_ptr<boy,person> paired1;
paired_ptr<person,boy> paired2;
paired1.connect(&paired2); // this shoudl work

Could you give an complete and concrete example needing polymorphysm that doesn works?

Best,
Vicente


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk