Boost logo

Boost :

Subject: Re: [boost] [paired ptr] Proposing a new smart pointer objectformanaging bidirectional relationships between objects
From: vicente.botet (vicente.botet_at_[hidden])
Date: 2010-05-03 20:21:20


----- Original Message -----
From: "Dan Walters" <dan683_at_[hidden]>
To: <boost_at_[hidden]>
Sent: Tuesday, May 04, 2010 12:54 AM
Subject: Re: [boost] [paired ptr] Proposing a new smart pointer objectformanaging bidirectional relationships between objects

>
> Thanks again to Rob for the additional feedback, and Vincente and
> Strasse for your feedback also.
>
> I'll try to respond to all in one big message.
>
> Rob:
>
>>That's an MSVC-specific warning. Use pragmas push, disable, and pop, conditionally compiled for MSVC, to manage that warning
>
> For MSVC, warning 4355 is disabled in the paired_ptr header.
>
>>No. I was implying "connect" and "disconnect" for those two.
>
> After replying to you, I went back to the source and it was obvious
> shorter names were more appropriate, and have updated already to
> connect and disconnect function names.
>
>>Using Boost.Function makes your callback support more flexible, which makes it more useful to users.
>
> Callback functions are now of type boost::function1<paired_ptr<T,U>*>
> where T is the owner type and U is the pointed to owner type. This
> works well as users can generate global functions to handle the
> callbacks, use static member functions, and even static member
> functions from other classes. Hence for member usage:
>
> class dog
> {public:
> static void connect_to_cat(paired_ptr<dog,cat>* p_paired_ptr) //
> paired_cat.set_connect_callback(&dog::connect_to_cat);
> {
> // to get the this pointer, use p_paired_ptr->owner()
> }
> };
>
> After consideration, I think this is a much more robust and flexible solution.
>
>>Don't assume that the object type for the callback member function pointer is the owner type.
>
> That was an ugly solution and another reason to use
> boost::function1<paired_ptr<T,U>*>
>
>>I fail to understand the problem. In each case you are invoking a functor with a reference to a paired_ptr. (You could even support overloading for const and non-const access.) What you need is type erasure to make the callback invocation ignorant of how the underlying function is invoked. IOW, you want a member function pointer invocation, via Boost.Function, say, to look just like calling a non-member function pointer or function object.
>
> The problem is that to call a normal function, the arguments would be:
>
> void callback_function(<paired_ptr<T,U>* p_ptr);
>
> For a member function it would be:
>
> void callback_function(T* this, <paired_ptr<T,U>* p_ptr);
>
> Obviously, the this pointer is available in member functions and so is
> passed invisibly to the function. So the parameter lists are different
> and not inter-operable between global functions and member functions.
> This can be overcome using std::bind1st.

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.
 
> I opt for settling for global functions and static member functions,
> as it is a simple and flexible solution.
>
> Thanks Rob, again your comments are gratefully received.
>
> Vicente:
>
>>I don't know why I don't like the name paired_ptr, one_to_one_ptr could be
> an alternative.
>
> How about bi_ptr? The object is then clearly a bidirectional pointer.
> one_to_one still seems ambiguous to me.

You are right one_to_one is no better. The class represents one of the ends of a bidirection association, mayb we have here the

> Infact, is this library even a pointer? It never manages memory in any
> way. Except for the -> operator and the get() function (like
> smart_ptrs) it doesn't really have any responsibilities that a pointer
> has, the name is only intended to make clear that it isn't the object
> itself. But a pointer is usually a container for a single object, and
> this is not a container. Maybe it is a reference? But this would
> confuse the -> operator and the get() function...
>
>>I will see this class as one more in a Association library
> that could include other kind of associations, as one_to_many, many_to_one,
> many_to_many,...
>
> While this immediately seems obvious, the one_to_one relationship is
> quite special. Here, both sides of the relationship share authority. A
> one to many or many to one relationship clearly has a single
> authoritative element - being the one rather than the many. How would
> these differ from a pointer container? And the many to many
> relationship is already dealt with in a way by the bimap library (as
> you later pointed out).
>
> The one-to-one relationship is a pointer to pointer, while any
> relationship involving a 'many' starts to become a container and in
> any container, there must be an authoritative element to manage the
> contents. Would it be possible for a group of elements to manage the
> pool that they reside in? Is this really useful in any way?
>
> To me, the one-to-one relationship is a special issue that can occur
> in the most simple of programs.
>
>>As no one side of the association is more important than the other maybe a non-member function could be more adequate. The connection between two objects can be established as so
>
>>connect(rex.paired_cat, kitty.paired_dog);
>
>>//connections can be severed at either end
>>disconnect(kitty.paired_dog);
>
> This is something I had already considered and have now wrapped
> paired_ptr.connect inside a global function as suggested, but have
> also left the paired_ptr.connect interface as a member also. I think
> both are useful, as the non-member function indicates the rationale of
> neither object being authoritative, while the connect member function
> indicates that either paired_ptr can initialize a connection.

I agree.
 
>>have you think about adding a mixin paired_ptr that can be used as base class and as such don't need to manage with the owner initialization?
>
> I had considered this. I think that as a member, the object is more
> useful as it represents a 'has' relationship rather than a 'is'
> relationship - composition is primary to the rationale.

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.
 
> Consider the following:
>
> class person
> {
> paired_ptr<person,person> father;
> };
>
> class person : public paired_ptr<person,person>
> {
> };
>
> Obviously the inherited version poorly represents the concept of a
> father, as it suggests the person is a special type of a link between
> two people.

I have though to this already. The association can be tagged, as it does for example multi-index and bimap.

struct father{};
class person : public paired_ptr<person,tagged<person,father>>
{
};

> It also limits the class to only one paired_ptr, and of only one pair of types.

And allows to have more than one person
class person :
    public paired_ptr<person, tagged<person,father>>,
    public paired_ptr<person, tagged<person,mother>>

{
};
 
> However, it would fix the issue of initializing the owner of the
> paired_ptr member.
>
>>one_to_one_ptr<T,U> could define a method to get the pointed U, get<U>
>
> A dynamic_cast version? I like that idea very much.

Not realy a dynamic_cast, just an overloading. Of course when tag are used, tags must be used.

p.get<father>();

>>There is also Boost.Bimap
>
> Thanks for reminding me. In a way, this library is a light weight
> version of bimap as it acts as a bimap holding two single values,
> mapping the type on the left to the type on the right. The advantage
> of paired_ptr is that there is no left and right, simply two ends of a
> link. paired_ptr is also a LOT simpler and hence much easier to use.

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.
 
> Thanks again Vicente, I found your feedback very useful.

You are welcome.
 
> Strasser:
>
>>reminds me of UML class diagram bi-directional associations.
>
> Thanks for pointing this out. I think that kind of relationship is a
> common requirement when programming and is so often worked around in a
> c++ environment.
>
>> if this makes sense for C++ (I'm not sure) you also might want to consider other types of associations, e.g. n-ary associations that are automatically kept consistent on both ends of the association.
>
> As Vicente mentioned, there is bimap that is a very complete
> bidirectional container library. I see the scope of this library as
> managing the one-to-one relationship. It is a pointer rather than a
> container, and as such, completes a very very different role. It has
> spawned from my thoughts on composition and the limitations of
> directional ownership.

I would say that your class can be seen as an *intrusive* way to implement a bidirectional association.

paired_ptr implements the to-one end part of one-to-one.
It would be great to have a class that implement the to-many end part of one-to-many and many-to-many.

class person :
    public to_one<person, tagged<person, father>>,
    public to_one<person, tagged<person, mother>>
    public to_many<person, tagged<person, childrens>>
{
};

Best regards,
____________________
Vicente Juan Botet Escribá
http://viboes.blogspot.com/


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