Boost logo

Boost :

Subject: Re: [boost] [paired ptr] Proposing a new smart pointer object formanaging bidirectional relationships between objects
From: Dan Walters (dan683_at_[hidden])
Date: 2010-05-03 18:54:51


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

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.

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

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.

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

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.

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

Thanks again Vicente, I found your feedback very useful.

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.

Thanks again you all of you for the feedback, i have found it to be a
huge help! I welcome any more comments :)

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