Boost logo

Boost :

From: Oleg Fedtchenko (BoostOleg_at_[hidden])
Date: 2004-10-12 09:52:15


For boost list moderators:
Please, replace the previous my email by this one as this email has the same content but the better formatting to view it in a Web-based mailbox.

>> Oleg Fedtchenko wrote:
>>
>> class DocActiveX
>> {
>> protected:
>> Tree* m_pTree;
>>
>> public:
>> ~DocActiveX(){ if(m_pTree) delete m_pTree;}
>> bool Load( const char* lpszPath);
>> Tree* Parse(){ return m_pTree;}
>> };

> Peter Dimov wrote:
>
> That's the easy part.
>
> class DocActiveX
> {
> private:
>
> shared_ptr<Tree> m_pTree;
>
> public:
>
> bool Load( const char* lpszPath );
> shared_ptr<Tree> Parse() { return m_pTree; }
> };

Ok. But if m_pTree has a pointer to its owner (of type DocActiveX) then it's possible that m_pTree will access through that pointer after its owner is deleted. Using shared_ptr to its owner will cause a deadlock (a member cannot have a shared_ptr to its owner).
 
Another task is:
 
class DocActiveX
{
private:

   Tree m_Tree; // it's an object, not a pointer

public:

   bool Load( const char* lpszPath );
   Tree* Parse() { return &m_Tree; }
};
 
To allow creating a safe shared_ptr pointing to a class member there is need in such a code:
 
    shared_ptr<Tree> Parse( shared_ptr<DocActiveX> ptrOwner)
    {
        shared_ptr<Tree> ptrMember( &m_Tree, ptrOwner);
        return ptrMember;
    }

And corresponding ctor that passes over a deletable pointer to an owner (ptrOwner) of a class member (p):
 
        template <class Y, class Z>
        shared_ptr( Y * p, shared_ptr<Z>& ptrOwner) : px(p), pn(ptrOwner.pn)
        {...}

It's good as it allows to use shared_ptr.
But it's good if this class will never be used as an object (nondynamic) data-member inside another owner.
Also it's good when we create shared_ptr using ptrOwner outside a class because we can pass in a shared_ptr of any owner.

But when we use shared_ptr inside a class (like in this example) it has three drawbacks.

The body of the function must be aware of the type of an owner. It prevents this code from being used in a DLL until the owner type is well-known (e.g. declared in boost).

Another thing is that it's impossible to use custom add_ref() and release() for an owner the way it is done in intrusive_ptr.
I mean a developer of a class must choose one of the shared_ptr and intrusive_ptr (if the last is extended to support a smart member pointers). But we want this class to be used inside owner of any type. And one owner will live with just *delete me* but another owner will require *let me do some processing and then delete me myself*.

And the third shortcoming is that it is clogging the code with the second parameter (for an owner shared_ptr) when there are many functions returning member pointers. It could be avoided by storing a pointer to an owner somewhere inside a class.

All of these drawbacks could be eliminated by using an owner pointer stored in a base class for every of the members and the owner itself. The base class would be of some predefined type. There could be a hierarchy of two base classes. One of them (member_base) stores a pointer to an owner (for both members and an owner). And the second (top) (owner_base) that stores a ref count and has overidable add_ref() and release() (for an owner only). Using overidable add_ref() and release() makes it possible to derive a class with these functions customized (like in intrusive_ptr).

Calling add_ref() and release() directly through an owner pointer allows to avoid writing additional intrusive_ptr_add_ref and intrusive_ptr_release functions for every owner type until you need some extra processing. Also you could create a set of owner_base derivative classes using classical polymorphism.

The idea of using a base class for intrusive_ptr was already spoken by Stefan Slapeta at http://lists.boost.org/MailArchives/boost/msg06973.php

Intrusive_ptr cannot be used directly as is for a member_ptr because a member_ptr must keep two different pointers: to a member (exposed by get()) and to a deletable owner for which add_ref() and release() are called. It must be a template of two arguments (the second is for a pointer to an owner).

Shared_ptr should not be used for this task in a whole as it doesn't allow to call custom add_ref().
And one more thing, to create a shared_ptr for a member we must pass to its ctor a smart pointer that keeps the use_count of an owner and is stored inside a class creating the smart pointer. Weak_ptr could be used for that as it doesn't cause a deadlock. But when we create shared_ptr based on an expired weak_ptr an exception is thrown. And weak_ptr is always expiredwhen an owner pointed to by this weak_ptr is created as an object data-member inside another upper-level owner, not by using op *new*.

Could you or anyone else say if this above and below is worth using?

Oleg Fedtchenko
BoostOleg_at_[hidden]

* * * * * * * * * * * * * * * * * * * * * * * * *
This is possible usage of member_ptr:

#include "member_ptr.hpp"

To allow creating a safe shared_ptr pointing to a class member there is

class A : public member_ptr_owner_base
{
public:
    A( member_ptr_owner_base* pOwner =0xFFFFFFFF)
    : member_ptr_owner_base( pOwner)
    { m_wData = 0;}

    member_ptr<WORD> GetData()
    {
// increments ref_count_ of the owner if it's not 0

        return member_ptr<WORD>(&m_wData, get_owner());
    }

protected:
    WORD m_wData;
};

class B : public member_ptr_owner_base
{
public:

// see ctor
// member_ptr_owner_base::this_type( this_type* pw)
// for meaning of 0xFFFFFFFF
//
// 0 should be passed when B is created as
// an object (nondynamic) data-member;
//
// you can pass in 0 for any owner
// or your owner must be member_ptr_owner_base derivative to handle
// references on it and for the class B could be placed inside a DLL
// (when its functions are not inlined)

    B( member_ptr_owner_base* pOwner =0xFFFFFFFF)
    : member_ptr_owner_base( pOwner),
         m_a(get_owner())
    {}

    shared_ptr<A> CreateA( int nOffset)
    {
        return shared_ptr<A>(new A);
    }

    member_ptr<A> CreateSafeA( int nOffset)
    {
        return member_ptr<A>(new A);

        // returned member_ptr has ref_count_ 1
        //
        // newly created A is an owner of itself,
        // ( see ctor member_ptr(T * px) )
        // it will be deleted when released
    }

    member_ptr<A> GetA()
    {
        return member_ptr<A>(&m_a, get_owner());
    
        // an owner will be deleted, not m_a
        //
        // but owner will be deleted only when
        // all member_ptrs to m_a are released
        //
        // if owner is 0 then nothing will be deleted
        // but &m_a will always be exposed
        // (member_ptr.get()...)

    }

    member_ptr<B> GetPtr()
    {
        return member_ptr<B>(this);
    }

protected:
    A m_a;
};

main()
{

// member_ptr_owner_base has already got
// intrusive_ptr_add_ref() and
// intrusive_ptr_release() for itself

    intrusive_ptr<B> pB = intrusive_ptr<B>(new B);

    WORD* pDataExcept = pB->CreateA( 15)->GetData();
    *pDataExcept = 57; // access violation

    member_ptr<WORD> pData = pB->CreateSafeA( 15)->GetData();
    *pData = 57; // safe access

    // ref_count_ changing

    // the next increments ref_count_ for pB from 1 to 2

    member_ptr<B> ptrB = pB->GetPtr();

    // the next increments ref_count_ for pB from 2 to 3

    member_ptr<A> ptrA = ptrB->GetA();
    *ptrA->GetData() = 91;

    // will print *91*

    std::cout << "data: " << *pB->GetA()->GetData() << '\n';
}

* * * * * * * * * * * * * * * * * * * * * * * * *
This is another possible usage of member_ptr:

(Pointer to an owner is released, but a pointer to its member is further used across a program)

class DocActiveX : public member_ptr_owner_base
{
protected:
    Tree* m_pTree;

public:
    DocActiveX(member_ptr_owner_base* pOwner)
    : member_ptr_owner_base(pOwner){}
    ~DocActiveX()
    {
        if(m_pTree) delete m_pTree;
    }
    bool Load( const char* lpszPath);
    member_ptr<Tree> Parse()
    {
        return member_ptr<Tree>(m_pTree, get_owner());
    }
};

class SomeClass
{
public:
    SomeClass() : m_pTree(this){}

protected:
    member_ptr<Tree> m_pTree;
};

void SomeClass::OnLoad()
{

// pInstance in the next is an owner of itself
// (see ctor member_ptr_owner_base::this_type( this_type* pw) )

    DocActiveX* pInstance = new DocActiveX;

    pInstance->Load( lpszSomePath);

    m_pTree = pInstance->Parse();

// m_pTree points to DocActiveX::m_pTree and has pInstance as its owner
//
// pInstance will be automatically deleted by SomeClass::m_pTree
// when SomeClass::m_pTree is released (when SomeClass is destructed
// if ashow.Show( m_pTree) did not pass m_pTree into another thread)

}

void SomeClass::OnShow()
{
    ShowExternal ashow;
    ashow.Show( m_pTree);
}

* * * * * * * * * * * * * * * * * * * * * * * * *
This is the member_ptr

Code for this smart pointer I created as an extension to intrusive_ptr (copyrighted by Peter Dimov) to hold two different pointers: to a member (exposed by get()) and to a deletable owner for which add_ref() and release() are called.

#ifndef BOOST_MEMBER_PTR_HPP_INCLUDED
#define BOOST_MEMBER_PTR_HPP_INCLUDED

//
// member_ptr.hpp
//
// This file is a derivative work from intrusive_ptr.hpp by Peter Dimov
//
// Copyright (c) 2004 Oleg Fedtchenko
//
// Permission to copy, use, modify, sell and distribute this software
// is granted provided this copyright notice appears in all copies.
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//

#include <boost/config.hpp>

#ifdef BOOST_MSVC // moved here to work around VC++ compiler crash
# pragma warning(push)
# pragma warning(disable:4284) // odd return type for operator->
#endif

#include <boost/assert.hpp>
#include <boost/detail/workaround.hpp>

#include <functional> // for std::less
#include <iosfwd> // for std::basic_ostream

namespace boost
{

//
// base classes used to work with member_ptr
//
//

//
// base class for all members
//

class member_ptr_owner_base;

class member_ptr_member_base
{
    typedef member_ptr_member_base this_type;
    typedef member_ptr_owner_base owner_type;

    owner_type* pw_;

public:
    this_type( owner_type* pw) : pw_(pw){}
    virtual ~member_ptr_member_base(){}
    virtual owner_type* get_owner() const{ return pw_;}
};

//
// base class for all owners
//

class member_ptr_owner_base : public member_ptr_member_base
{
    typedef member_ptr_member_base base_type;
    typedef member_ptr_owner_base this_type;

protected:
    int ref_count_;

public:

    this_type( this_type* pw)
    :base_type( (pw == (this_type*)0xFFFFFFFF) ? this : pw), ref_count_(0)
    {}
    virtual ~member_ptr_owner_base(){}

    virtual void add_ref()
    {
        ++ref_count_;
    }

    virtual void release()
    {
        if (--ref_count_ == 0)
        {
            delete this;
        }
    }

    virtual int get_ref_count() const
    {
        return ref_count_;
    }
};

//
// these functions are called by
// intrusive_ptr<member_ptr_owner_base>
//

inline void intrusive_ptr_add_ref(member_ptr_owner_base * p)
{
    p->add_ref();
}
inline void intrusive_ptr_release(member_ptr_owner_base * p)
{
    p->release();
}

//
// member_ptr
//
// A smart pointer that uses intrusive-like reference counting for
// an owner of a class data-member.
//
// The object is responsible for destroying itself
// (if member_ptr_owner_base is not its direct ancestor).
//

template<class T> class member_ptr
{
private:

    typedef member_ptr this_type;

public:

    typedef T member_type;
    typedef member_ptr_owner_base owner_type;

    member_ptr(): px_(0), pw_(0)
    {
    }

    member_ptr(T * px, owner_type * pw, bool to_add_ref = true)
    : px_(px), pw_(pw)
    {
        if( to_add_ref) add_ref();
    }

    member_ptr(T * px): px_(px), pw_(px->get_owner())
    {
        add_ref();
    }

#if !defined(BOOST_NO_MEMBER_TEMPLATES) || defined(BOOST_MSVC6_MEMBER_TEMPLATES)

    template<class U> member_ptr(member_ptr<U> const & rhs)
    : px_(rhs.get()), pw_(rhs.get_owner())
    {
        add_ref();
    }

#endif

    member_ptr(member_ptr const & rhs): px_(rhs.px_), pw_(rhs.pw_)
    {
        add_ref();
    }

    ~member_ptr()
    {
        release_ref();
    }

    void add_ref()
    {
        if( pw_ != 0 &&
            // to avoid exception when using member_ptr inside
            // an owner ctor
            ( ((void*)px_ == (void*)pw_) || pw_->get_ref_count()>0))
        {
            pw_->add_ref();
        }
    }

    void release_ref()
    {
        if(pw_ != 0 && pw_->get_ref_count() > 0)
        {
            pw_->release();
        }
    }

#if !defined(BOOST_NO_MEMBER_TEMPLATES) || defined(BOOST_MSVC6_MEMBER_TEMPLATES)

    template<class U> member_ptr & operator=(member_ptr<U> const & rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }

#endif

    member_ptr & operator=(member_ptr const & rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }

/* unsafe - there is no owner specified

    member_ptr & operator=(T * rhs)
    {
        this_type(rhs).swap(*this);
        return *this;
    }
*/
    T * get() const
    {
        return px_;
    }

    owner_type * get_owner() const
    {
        return pw_;
    }

    T & operator*() const
    {
        return *px_;
    }

    T * operator->() const
    {
        return px_;
    }

#if defined(__SUNPRO_CC) && BOOST_WORKAROUND(__SUNPRO_CC, <= 0x530)

    operator bool () const
    {
        return px_ != 0;
    }

#elif defined(__MWERKS__) && BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3003))
    typedef T * (this_type::*unspecified_bool_type)() const;

    operator unspecified_bool_type() const // never throws
    {
        return px_ == 0? 0: &this_type::get;
    }

#else

    typedef T * this_type::*unspecified_bool_type;

    operator unspecified_bool_type () const
    {
        return px_ == 0? 0: &this_type::px_;
    }

#endif

    // operator! is a Borland-specific workaround
    bool operator! () const
    {
        return px_ == 0;
    }

    void swap(member_ptr & rhs)
    {
        T * tmpX = px_;
        owner_type * tmpW = pw_;
        px_ = rhs.px_;
        pw_ = rhs.pw_;
        rhs.px_ = tmpX;
        rhs.pw_ = tmpW;
    }

private:

    T * px_;
    owner_type * pw_;
};

template<class T, class U>
inline bool operator==(member_ptr<T> const & a, member_ptr<U> const & b)
{
    return a.get() == b.get();
}

template<class T, class U>
inline bool operator!=(member_ptr<T> const & a, member_ptr<U> const & b)
{
    return a.get() != b.get();
}

template<class T>
inline bool operator==(member_ptr<T> const & a, T * b)
{
    return a.get() == b;
}

template<class T>
inline bool operator!=(member_ptr<T> const & a, T * b)
{
    return a.get() != b;
}

template<class T>
inline bool operator==(T * a, member_ptr<T> const & b)
{
    return a == b.get();
}

template<class T>
inline bool operator!=(T * a, member_ptr<T> const & b)
{
    return a != b.get();
}

#if __GNUC__ == 2 && __GNUC_MINOR__ <= 96

// Resolve the ambiguity between our op!= and the one in rel_ops

template<class T>
inline bool operator!=(member_ptr<T> const & a, member_ptr<T> const & b)
{
    return a.get() != b.get();
}

#endif

template<class T>
inline bool operator<(member_ptr<T> const & a, member_ptr<T> const & b)
{
    return std::less<T *>()(a.get(), b.get());
}

template<class T>
void swap(member_ptr<T> & lhs, member_ptr<T> & rhs)
{
    lhs.swap(rhs);
}

// mem_fn support

template<class T>
T * get_pointer(member_ptr<T> const & p)
{
    return p.get();
}

template<class T>
member_ptr<T>::owner_type * get_owner(member_ptr<T> const & p)
{
    return p.get_owner();
}

template<class T, class U>
member_ptr<T> static_pointer_cast(member_ptr<U> const & p)
{
    return member_ptr<T,W>( static_cast<T *>(p.get()),
                            static_cast<member_ptr<T>::owner_type *>
                            (p.get_owner()));
}

template<class T, class U>
member_ptr<T> const_pointer_cast(member_ptr<U> const & p)
{
    return member_ptr<T,W>( const_cast<T *>(p.get()),
                            const_cast<member_ptr<T>::owner_type *>
                            (p.get_owner()));
}

template<class T, class U>
member_ptr<T> dynamic_pointer_cast(member_ptr<U> const & p)
{
    return member_ptr<T>( dynamic_cast<T *>(p.get()),
                          dynamic_cast<member_ptr<T>::owner_type *>
                          (p.get_owner()));
}

// operator<<

#if defined(__GNUC__) && (__GNUC__ < 3)

template<class Y>
std::ostream & operator<< (std::ostream & os, member_ptr<Y> const & p)
{
    os << p.get();
    return os;
}

#else

# if defined(BOOST_MSVC) && BOOST_WORKAROUND(BOOST_MSVC, <= 1200 && __SGI_STL_PORT)
// MSVC6 has problems finding std::basic_ostream through the using declaration in namespace _STL
using std::basic_ostream;
template<class E, class T, class Y>
basic_ostream<E, T> & operator<< (basic_ostream<E, T> & os, member_ptr<Y> const & p)
# else
template<class E, class T, class Y>
std::basic_ostream<E, T> & operator<< (std::basic_ostream<E, T> & os, member_ptr<Y> const & p)
# endif
{
    os << p.get();
    return os;
}

#endif

} // namespace boost

#ifdef BOOST_MSVC
# pragma warning(pop)
#endif

#endif // #ifndef BOOST_MEMBER_PTR_HPP_INCLUDED

// end of member_ptr


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk