boost::shared_ptr and multiple inheritance

Discussion in 'C++' started by EnsGabe, Jun 22, 2008.

  1. EnsGabe

    EnsGabe Guest

    Suppose you have a class heirarchy as such:


    class Base{
    ....
    };


    class Mid1 : public Base{
    ....
    };

    class Mid2 : public Base{
    ....
    };


    class Derived: public Mid1, Mid2{


    };

    Mid1, Mid2, and Base are ABC.

    What is an effective way to manage the lifetime of this object?
    Currently, I leak Derived (and anything similar to Derived) all over
    the place by not reclaiming them at all. That works for now, in as
    much as when I am done with them I am done with execution of my
    program, but is not a solution that will work for the future.

    My use at the moment is as such:

    class ObjectHolder {
    public:
    ....
    void addMid1(Mid1 *p) { assert(p); mid1s.push_back(p); };
    void addMid2(Mid2 *p) { assert(p); mid2s.push_back(p); };
    private:
    std::vector<Mid1*> mid1s;
    std::vector<Mid2*> mid2s;
    ....
    };

    There will be many different classes that are equivalent to Derived
    (in the sense that they are deriving from Mid1 and Mid2.) There will
    also be classes that derive from one or the other of Mid1 and Mid2.
    All of them will be registered exactly once in mid1s and/or mid2s,
    whichever they are derived from. References to the objects will not
    exist outside of ObjectHolder. Objects may contain references to
    other objects; the directed graph that they form will be acyclic.

    My first instinct was to delete every element of mid1s and mid2s in
    the destructor for ObjectHolder, but that will not work when a Derived
    has been inserted into both vectors. My next idea was to use
    boost::shared_ptr and use that to automatically manage the lifetime of
    the object, but there was nothing in the documentation that I saw that
    led me to believe that one could use a shared_ptr across a class
    heirarchy. Since the 'this' pointer for an object is different
    depending on the type it is being used as, I'm (possibly erroneously)
    assuming that a shared_ptr will mismanage the reference count for the
    pointer since they are using different pointers. Am I wrong in my
    understanding of the semantics of boost::shared_ptr?

    (PS: It's obvious that what would solve my problem is a GC- I'm not
    ruling it out at this point, but I am exploring my options)
     
    EnsGabe, Jun 22, 2008
    #1
    1. Advertising

  2. EnsGabe

    Kai-Uwe Bux Guest

    EnsGabe wrote:

    > Suppose you have a class heirarchy as such:
    >
    >
    > class Base{
    > ...
    > };
    >
    >
    > class Mid1 : public Base{
    > ...
    > };
    >
    > class Mid2 : public Base{
    > ...
    > };
    >
    >
    > class Derived: public Mid1, Mid2{
    >
    >
    > };
    >
    > Mid1, Mid2, and Base are ABC.
    >
    > What is an effective way to manage the lifetime of this object?
    > Currently, I leak Derived (and anything similar to Derived) all over
    > the place by not reclaiming them at all. That works for now, in as
    > much as when I am done with them I am done with execution of my
    > program, but is not a solution that will work for the future.
    >
    > My use at the moment is as such:
    >
    > class ObjectHolder {
    > public:
    > ...
    > void addMid1(Mid1 *p) { assert(p); mid1s.push_back(p); };
    > void addMid2(Mid2 *p) { assert(p); mid2s.push_back(p); };
    > private:
    > std::vector<Mid1*> mid1s;
    > std::vector<Mid2*> mid2s;
    > ...
    > };
    >
    > There will be many different classes that are equivalent to Derived
    > (in the sense that they are deriving from Mid1 and Mid2.) There will
    > also be classes that derive from one or the other of Mid1 and Mid2.
    > All of them will be registered exactly once in mid1s and/or mid2s,
    > whichever they are derived from. References to the objects will not
    > exist outside of ObjectHolder. Objects may contain references to
    > other objects; the directed graph that they form will be acyclic.
    >
    > My first instinct was to delete every element of mid1s and mid2s in
    > the destructor for ObjectHolder, but that will not work when a Derived
    > has been inserted into both vectors.


    The following is based on the idea that the objects should unregister
    themselves upon destruction.

    #include <set>
    #include <iostream>

    class Owner;

    class BaseA {

    Owner * owner_ptr;

    public:

    virtual
    ~BaseA ( void );

    void register_owner ( Owner * p );

    };

    class BaseB {

    Owner * owner_ptr;

    public:

    virtual
    ~BaseB ( void );

    void register_owner ( Owner * p );

    };

    class Owner {

    std::set< BaseA * > all_a;
    std::set< BaseB * > all_b;

    public:

    void register_a ( BaseA * p ) {
    all_a.insert( p );
    p->register_owner( this );
    }

    void register_b ( BaseB * p ) {
    all_b.insert( p );
    p->register_owner( this );
    }

    void unregister_a ( BaseA * p ) {
    all_a.erase( p );
    }

    void unregister_b ( BaseB * p ) {
    all_b.erase( p );
    }

    ~Owner ( void ) {
    while ( ! all_a.empty() ) {
    std::set< BaseA * >::iterator first = all_a.begin();
    BaseA * a_ptr = *first;
    all_a.erase( first );
    delete a_ptr;
    }
    while ( ! all_b.empty() ) {
    std::set< BaseB * >::iterator first = all_b.begin();
    BaseB * b_ptr = *first;
    all_b.erase( first );
    delete b_ptr;
    }
    }

    };


    BaseA::~BaseA ( void ) {
    owner_ptr->unregister_a( this );
    std::cout << "a";
    }

    void BaseA::register_owner ( Owner * p ) {
    owner_ptr = p;
    }


    BaseB::~BaseB ( void ) {
    owner_ptr->unregister_b( this );
    }

    void BaseB::register_owner ( Owner * p ) {
    owner_ptr = p;
    std::cout << "b";
    }

    class X : public BaseA, public BaseB {

    virtual
    ~X ( void ) {
    std::cout << "x";
    }

    };

    int main ( void ) {
    Owner o;
    for ( unsigned int i = 0; i < 10; ++i ) {
    X * x = new X;
    o.register_a( x );
    o.register_b( x );
    BaseA * a = new BaseA;
    o.register_a( a );
    BaseB * b = new BaseB;
    o.register_b( b );
    }
    }

    > My next idea was to use
    > boost::shared_ptr and use that to automatically manage the lifetime of
    > the object, but there was nothing in the documentation that I saw that
    > led me to believe that one could use a shared_ptr across a class
    > heirarchy. Since the 'this' pointer for an object is different
    > depending on the type it is being used as, I'm (possibly erroneously)
    > assuming that a shared_ptr will mismanage the reference count for the
    > pointer since they are using different pointers. Am I wrong in my
    > understanding of the semantics of boost::shared_ptr?

    [snip]

    If D is derived from T, then tr1::shared_ptr<T> allows assignment and copy
    construction from tr1::shared_ptr<D>. So, if you do:

    std::vector< shared_ptr<Mid1> > mid1s;
    std::vector< shared_ptr<Mid2> > mid2s;

    and

    void addMid1 ( shared_ptr<Mid1> p );
    void addMid2 ( shared_ptr<Mid2> p );

    you can use it as follows:

    shared_ptr< Derived > d_ptr = new Derived ( ... );
    addMid1( d_ptr );
    addMid2( d_ptr );

    and it should work just fine. Have you tried?



    Best

    Kai-Uwe Bux
     
    Kai-Uwe Bux, Jun 22, 2008
    #2
    1. Advertising

  3. EnsGabe

    EnsGabe Guest

    On Jun 22, 4:11 am, Kai-Uwe Bux <> wrote:

    > The following is based on the idea that the objects should unregister
    > themselves upon destruction.
    >
    > #include <set>
    > #include <iostream>
    >
    > class Owner;
    >
    > class BaseA {
    >
    >   Owner * owner_ptr;
    >
    > public:
    >
    >   virtual
    >   ~BaseA ( void );
    >
    >   void register_owner ( Owner * p );
    >
    > };
    >
    > class BaseB {
    >
    >   Owner * owner_ptr;
    >
    > public:
    >
    >   virtual
    >   ~BaseB ( void );
    >
    >   void register_owner ( Owner * p );
    >
    > };
    >
    > class Owner {
    >
    >   std::set< BaseA * > all_a;
    >   std::set< BaseB * > all_b;
    >
    > public:
    >
    >   void register_a ( BaseA * p ) {
    >     all_a.insert( p );
    >     p->register_owner( this );
    >   }
    >
    >   void register_b ( BaseB * p ) {
    >     all_b.insert( p );
    >     p->register_owner( this );
    >   }
    >
    >   void unregister_a ( BaseA * p ) {
    >     all_a.erase( p );
    >   }
    >
    >   void unregister_b ( BaseB * p ) {
    >     all_b.erase( p );
    >   }
    >
    >   ~Owner ( void ) {
    >     while ( ! all_a.empty() ) {
    >       std::set< BaseA * >::iterator first = all_a.begin();
    >       BaseA * a_ptr = *first;
    >       all_a.erase( first );
    >       delete a_ptr;
    >     }
    >     while ( ! all_b.empty() ) {
    >       std::set< BaseB * >::iterator first = all_b.begin();
    >       BaseB * b_ptr = *first;
    >       all_b.erase( first );
    >       delete b_ptr;
    >     }
    >   }
    >
    > };
    >
    > BaseA::~BaseA ( void ) {
    >   owner_ptr->unregister_a( this );
    >   std::cout << "a";
    >
    > }
    >
    > void BaseA::register_owner ( Owner * p ) {
    >   owner_ptr = p;
    >
    > }
    >
    > BaseB::~BaseB ( void ) {
    >   owner_ptr->unregister_b( this );
    >
    > }
    >
    > void BaseB::register_owner ( Owner * p ) {
    >   owner_ptr = p;
    >   std::cout << "b";
    >
    > }
    >
    > class X : public BaseA, public BaseB {
    >
    >   virtual
    >   ~X ( void ) {
    >     std::cout << "x";
    >   }
    >
    > };
    >
    > int main ( void ) {
    >   Owner o;
    >   for ( unsigned int i = 0; i < 10; ++i ) {
    >     X * x = new X;
    >     o.register_a( x );
    >     o.register_b( x );
    >     BaseA * a = new BaseA;
    >     o.register_a( a );
    >     BaseB * b = new BaseB;
    >     o.register_b( b );
    >   }
    >
    > }


    Interesting. This couples the classes together, though. I'd like to
    avoid that.


    >
    > If D is derived from T, then tr1::shared_ptr<T> allows assignment and copy
    > construction from tr1::shared_ptr<D>. So, if you do:
    >
    >   std::vector< shared_ptr<Mid1> > mid1s;
    >   std::vector< shared_ptr<Mid2> > mid2s;
    >
    > and
    >
    >   void addMid1 ( shared_ptr<Mid1> p );
    >   void addMid2 ( shared_ptr<Mid2> p );
    >
    > you can use it as follows:
    >
    >   shared_ptr< Derived > d_ptr = new Derived ( ... );
    >   addMid1( d_ptr );
    >   addMid2( d_ptr );
    >
    > and it should work just fine. Have you tried?
    >
    > Best
    >
    > Kai-Uwe Bux


    I've tried it, and it compiles, but I learned long ago that is a far
    cry from saying that something is working. Can you point me in the
    direction of documentation for this?
     
    EnsGabe, Jun 22, 2008
    #3
  4. EnsGabe

    Kai-Uwe Bux Guest

    EnsGabe wrote:

    [snip]
    >>
    >> If D is derived from T, then tr1::shared_ptr<T> allows assignment and
    >> copy construction from tr1::shared_ptr<D>. So, if you do:
    >>
    >> std::vector< shared_ptr<Mid1> > mid1s;
    >> std::vector< shared_ptr<Mid2> > mid2s;
    >>
    >> and
    >>
    >> void addMid1 ( shared_ptr<Mid1> p );
    >> void addMid2 ( shared_ptr<Mid2> p );
    >>
    >> you can use it as follows:
    >>
    >> shared_ptr< Derived > d_ptr = new Derived ( ... );
    >> addMid1( d_ptr );
    >> addMid2( d_ptr );
    >>
    >> and it should work just fine. Have you tried?
    >>
    >> Best
    >>
    >> Kai-Uwe Bux

    >
    > I've tried it, and it compiles, but I learned long ago that is a far
    > cry from saying that something is working. Can you point me in the
    > direction of documentation for this?


    I am looking at the draft n2521 for C++0X. There, you will find what you are
    looking for in clause [20.6.6.2.1/19-22]:

    shared_ptr(shared_ptr const& r);
    template<class Y> shared_ptr(shared_ptr<Y> const& r);
    Requires: For the second constructor Y* shall be convertible to T*.
    Effects: If r is empty, constructs an empty shared_ptr object; otherwise,
    constructs a shared_ptr object that shares ownership with r.
    Postconditions: get() == r.get() && use_count() == r.use_count().
    Throws: nothing.
    ...

    Note the postconditions.

    Assignment is dealt with in [20.6.6.2.3]. Whichever version of shared_ptr<>
    you are using, look up the documentation of copy-construction and
    assignment.



    Best

    Kai-Uwe Bux
     
    Kai-Uwe Bux, Jun 22, 2008
    #4
  5. EnsGabe

    James Kanze Guest

    On Jun 22, 8:23 am, EnsGabe <> wrote:
    > Suppose you have a class heirarchy as such:


    > class Base{
    > ...
    > };


    > class Mid1 : public Base{
    > ...
    > };


    > class Mid2 : public Base{
    > ...
    > };


    > class Derived: public Mid1, Mid2{
    > };


    > Mid1, Mid2, and Base are ABC.


    Are you sure that you want several instances of Base in the
    final object? Or do you want virtual inheritance.

    > What is an effective way to manage the lifetime of this object?


    In what way? How you manage lifetime of an object depends on
    the semantics of the object.

    > Currently, I leak Derived (and anything similar to Derived)
    > all over the place by not reclaiming them at all.


    You mean you never call delete on them. (If the objects don't
    have a deterministic lifetime, requiring a explicit call to
    delete, then garbage collection is the obvious answer.)

    > That works for now, in as much as when I am done with them I
    > am done with execution of my program, but is not a solution
    > that will work for the future.


    > My use at the moment is as such:


    > class ObjectHolder {
    > public:
    > ...
    > void addMid1(Mid1 *p) { assert(p); mid1s.push_back(p); };
    > void addMid2(Mid2 *p) { assert(p); mid2s.push_back(p); };
    > private:
    > std::vector<Mid1*> mid1s;
    > std::vector<Mid2*> mid2s;
    > ...
    > };


    > There will be many different classes that are equivalent to
    > Derived (in the sense that they are deriving from Mid1 and
    > Mid2.) There will also be classes that derive from one or the
    > other of Mid1 and Mid2. All of them will be registered
    > exactly once in mid1s and/or mid2s, whichever they are derived
    > from. References to the objects will not exist outside of
    > ObjectHolder. Objects may contain references to other
    > objects; the directed graph that they form will be acyclic.


    In which case, reference counted pointers can be used as a
    substitute for garbage collection. It's more invasive, and
    usually somewhat slower, but installing boost::shared_ptr is a
    lot easier than installing the Boehm collector.

    > My first instinct was to delete every element of mid1s and
    > mid2s in the destructor for ObjectHolder, but that will not
    > work when a Derived has been inserted into both vectors. My
    > next idea was to use boost::shared_ptr and use that to
    > automatically manage the lifetime of the object, but there was
    > nothing in the documentation that I saw that led me to believe
    > that one could use a shared_ptr across a class heirarchy.


    You can, provided that all of the instances of shared_ptr derive
    from the same initial shared_ptr. (What you can't do is create
    an initial shared_ptr twice from a raw pointer. Basically,
    you should probably create the initial shared_ptr as a
    shared_ptr< Derived >, at the site of the new, and use nothing
    but shared_ptr after that---shared_ptr supports all of the usual
    pointer conversions, or it should.)

    > Since the 'this' pointer for an object is different
    > depending on the type it is being used as, I'm (possibly erroneously)
    > assuming that a shared_ptr will mismanage the reference count for the
    > pointer since they are using different pointers. Am I wrong in my
    > understanding of the semantics of boost::shared_ptr?


    Sort of. Globally, there are two large families of reference
    counted pointers: invasive and non-invasive. Since you're
    dealing with a known hierarchy, you can easily use either. The
    invasive pointers have the advantage that you can create new
    smart pointers from the raw pointer anywhere you want; you
    could, for example, have the only smart pointers in the two
    containers, and that would work. On the other hand, when
    multiple inheritance is involved, the base class (which derived
    from the RefCntObj, or whatever) *must* be virtual, period.
    Non-invasive pointers have the advantage that the pointed to
    object doesn't need to be aware that reference counted pointers
    are being used---you can even create a shared_ptr<int> (not that
    there would ever be any reason to). On the other hand, every
    time you create an instance of the smart pointer from a raw
    pointer, you get a new counter.

    Which is more appropriate in your case depends on the way the
    objects are allocated and who manages the containers.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, Jun 22, 2008
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Derek
    Replies:
    2
    Views:
    629
    Derek
    Dec 8, 2003
  2. Ryan Mitchley
    Replies:
    6
    Views:
    11,170
    Maxim Yegorushkin
    Jun 26, 2004
  3. =?UTF-8?B?UmFmYcWCIE1haiBSYWYyNTY=?=

    boost::weak_ptr and shared_ptr pointers from "this"

    =?UTF-8?B?UmFmYcWCIE1haiBSYWYyNTY=?=, Apr 5, 2006, in forum: C++
    Replies:
    2
    Views:
    1,244
    Joe Gottman
    Apr 6, 2006
  4. Toby Bradshaw
    Replies:
    6
    Views:
    1,757
    Kai-Uwe Bux
    Jun 2, 2006
  5. Colin Caughie
    Replies:
    1
    Views:
    721
    Shooting
    Aug 29, 2006
Loading...

Share This Page