#include <hello>
I was recently having a case where I was coupling two objects loosely. Both
needed to know about each other, but the connection was not static, they
sometimes where separated and reconnected somewhere else. For the curious, I
was connecting a propertylist-widget with a container of entries for the list.
I currently fixed the issue by making some friend-declarations, some asserts
and just 'paying attention that it works', but this is of course not
satisfactory. I thus started thinking about a bidi_ptr<> template....
Any suggestions ? Is this a worthwhile idiom ?
cheers
Ulrich Eckhardt
Outline:
- takes two types as template-arguments, the types of the owners that are to
be connected.
- bidi_ptr<>s come in pairs, bidi_ptr<A,B> is complementary to bidi_ptr<B,A>
- every bidi_ptr mainly consists of two pointers, one points to the owning
object (m_owner), the other to the complementary bidi_ptr<> (m_peer)
- assignment is defined as 'void bidi_ptr<A,B>::operator=(bidi_ptr<B,A>&rhs)'
Note that is doesn't return a reference and that 'rhs' is not a const
reference. This is used to 'connect' two peers. Also note that 'a = b' has
the same effect as 'b = a'.
- destruction of either bidi_ptr results in the peer being nulled
- redirecting a pointer to a new peer nulls the former peer
- pointer-like behaviour (*,->,get()) is implemented by accessing
'm_peer->m_owner'
- operator! and conversion to a boolean are mostly copied from
boost::shared_ptr
TODO:
- (how) can I implement that I can connect a bidi_ptr<A, Base> to a
bidi_ptr<Derived,A> ?
- bidi_ptr needs to be initialized with 'this' and store that pointer. I'm a
bit dissatisfied with that, because it effectively doubles the
memory-requirement and is not necessary (oversimplified example: 'owner =
this-0x8') since it should easily be computed.
- I have no idea if operator safe_bool() is properly implemented, because I
don't understand the whys and hows of it, anyone a pointer to the rationale
for it ?
- using assignment for connecting peers warps the 'normal' modus operandi of
assignment, should I rather us an auxillary function ?
- usually, I would make bidi_ptr<B,A> a friend of bidi_ptr<A,B>, because it
needs access to its peers internals in order to not work recursively. This
doesn't work with my compiler if B is the same as A, the compiler complains
that any class is implicitly its own friend. Therefore all data is currently
public.
- what should operator-> or * do when there is no peer attached ?
- provide an implementation that uses a non-template base-class with two
void-pointers, with derived classes only doing the casts. Measure executable
size with and without.
#include "bidi_ptr.h"
#include <iostream>
struct X;
struct Y;
struct Z;
struct X
{
X():m_pY(this), m_pX(this){}
void test() const;
void notify() const
{ std::cerr << "X::notify()\n"; }
bidi_ptr<X,Y> m_pY;
bidi_ptr<X,X> m_pX;
};
struct Y
{
Y():m_pX(this){}
void notify() const
{ std::cerr << "Y::notify()\n"; }
bidi_ptr<Y,X> m_pX;
};
void X::test() const
{
std::cerr << "X::test()\n";
if(m_pX)
m_pX->notify();
if(m_pY)
m_pY->notify();
}
int main()
{
X x;
{
Y y;
connect(x.m_pY, y.m_pX);
connect(x.m_pX, x.m_pX);
x.test();
}
x.test();
return 0;
}
#ifndef BIDI_SMART_POINTER_H
#define BIDI_SMART_POINTER_H
#include <cassert>
template<typename ThisType, typename OtherType>
class bidi_ptr
{
typedef bidi_ptr<ThisType,OtherType> this_type;
typedef bidi_ptr<OtherType,ThisType> complement_type;
// not default-constructible
bidi_ptr();
public:
/** you always have to init a bidi-pointer with the owning object,
i.e. this. */
bidi_ptr(ThisType* owner):m_owner(owner),m_peer(0)
{ assert(m_owner);}
/** connect two pointers
Note:
- 'p1 = p2' as the same postcondition as 'p2 = p1'
- return-type is void because expressions like 'p1=p2=p3' make no sense
- the other pointer is taken by non-const reference
*/
void operator=(bidi_ptr<OtherType,ThisType>& other)
{
if(m_peer!=&other)
{
other.reset();
reset();
m_peer = &other;
other.m_peer = this;
}
}
// friend class bidi_ptr<OtherType,ThisType>;
~bidi_ptr()
{ reset(); }
void reset()
{
if(m_peer)
m_peer->m_peer = 0;
m_peer = 0;
}
typedef bool (this_type::*safe_bool)() const;
operator safe_bool() const
{
return m_peer?&this_type::operator!:0;
}
bool operator!()const
{ return m_peer == 0; }
OtherType* get() const
{
assert_valid();
return m_peer?m_peer->m_owner:0;
}
OtherType* operator->() const
{
return get();
}
OtherType& operator*() const
{
return *get();
}
// private:
// - must have owner
// - if connected, peer must be connected to this
void assert_valid() const
{
assert(m_owner);
if(m_peer)
assert(this == m_peer->m_peer);
}
ThisType*const m_owner;
bidi_ptr<OtherType,ThisType>* m_peer;
};
template<typename ThisType, typename OtherType>
void connect(bidi_ptr<OtherType,ThisType>& p1, bidi_ptr<ThisType,OtherType>& p2)
{
p1 = p2;
}
#endif
_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost