#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

Reply via email to