
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.
I will see this class as one more in a Association library
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... 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