Boost logo

Boost :

From: Michel André (michel.andre_at_[hidden])
Date: 1999-11-23 14:40:34


Maybe an explanation and a backgrounder is in place.

valentin bonnard <bonnard.-_at_[hidden]> wrote:
original article:http://www.egroups.com/group/boost/?start=987
> Howard Hinnant wrote:
>
> > template<typename T , typename Deleter = deleter<T> >
> > class base_ptr
>
> > template<typename T>
> > class base_ptr<T, void (*)(T*)>
>
> May I ask what that is for ?
>
> (I am not tired, so forgive if it's completely obvious.)

Greg, Howard and others correct me if i'm wrong.
I've used the boost smart ptr classes in my project and came
up with an idea that we could templatize and parametrize the
delete action in the smart ptr classes. The rationale behind this
mainly to reduce code of the smart_ptr classes. And the spin off
is that there are other kinds of delete operations than delete and
delete[] for example the win32 function GlobalFree could be used if
the pointer sent in to the smart_ptr object to was allocated by
GlobalAlloc.
In short there is a lot of ways to allocate memory and equally many
ways to free a pointer to that memory.

And after some e-mail discussions between Howard, Greg and me we
came up with a solution that i have implemented and tried at home.
But basically what we've worked with so far is an implementation based
on sending in a deleter unary_function class wich does the actual
deleting
of the pointer. The base_ptr class encapsulates a pointer and the
deleter for an pointer and is used in the implementation of scoped_ptr,
scoped_array,shared_ptr and shared_array. Anyways what we ended up with
was something that look like this:

template<typename T> class deleter_function : public
unary_function<T*&,void>
{
public:

};

template<typename T> class deleter : public deleter_function<T>
{
public:
        void operator()(T*& p) throw() { delete p; p = NULL; };
};

template<typename T> class array_deleter : public deleter_function<T>
{
public:
        void operator()(T*& p) throw() { delete[] p; p = NULL; };
};

template <typename Base, typename Member> struct base_opt : Base {
    Member m;
    base_opt(Base const& b, Member const& m) : Base(b), m(m) {}
        base_opt(const base_opt& rhs) throw() : Base(rhs) { m = rhs.m; }
};
/*
template<typename T , typename Deleter> class base_ptr : noncopyable
{
public:
        typedef T element_type;

        T& operator*() const throw() { return *ptr(); }
        T* operator->() const throw() { return ptr(); }
        T* get() const throw() { return ptr(); }

protected:
    T*& ptr() { return deleter.m; }
        T* ptr() const { return deleter.m; }
    void del() { deleter.del(deleter.m); }
        void del(T* p) { deleter.del(p); };
    base_ptr(T* p,Deleter d) throw() : deleter(_Deleter(d),p) {};
        base_ptr(const base_ptr& rhs) throw() : deleter(rhs.deleter) { }

private:
        struct _Deleter {
                _Deleter(const Deleter& d) : del(d) {}
                Deleter del;
        };
        base_opt< _Deleter, T* > deleter;
};
*/

template<typename T , typename Deleter> class base_ptr : noncopyable
{
public:
        typedef T element_type;

        T& operator*() const throw() { return *ptr(); }
        T* operator->() const throw() { return ptr(); }
        T* get() const throw() { return ptr(); }

protected:
    T*& ptr() { return deleter.m; }
        T* ptr() const { return deleter.m; }
    void del() { deleter(deleter.m); }
        void del(T* p) { deleter(p); };
    base_ptr(T* p,Deleter d) throw() : deleter(d,p) {};
        base_ptr(const base_ptr& rhs) throw() : deleter(rhs.deleter) { }

private:
        
        base_opt< Deleter, T* > deleter;
};

// scoped_ptr -------------------------------------------------------
-------//
// scoped_ptr mimics a built-in pointer except that it guarantees
deletion
// of the object pointed to, either on destruction of the scoped_ptr
or via
// an explicit reset(). scoped_ptr is a simple solution for simple
needs;
// see shared_ptr (below) or std::auto_ptr if your needs are more
complex.
template< typename T , typename Deleter = deleter<T> > class scoped_ptr
: public base_ptr< T , Deleter >
{
public:
        typedef T element_type;

        explicit scoped_ptr( T* p=0, const Deleter& d = deleter<T>()) throw()
: base_ptr<T,Deleter>(p,d) {}
        ~scoped_ptr() { reset(); }

        void reset( T* p=0 ) {
                if ( ptr() != p )
                {
                        del();
                        ptr() = p;
                }
        }
}; // scoped_ptr

// scoped_array -----------------------------------------------------
-------//
// scoped_array extends scoped_ptr to arrays. Deletion of the array
pointed to
// is guaranteed, either on destruction of the scoped_array or via an
explicit
// reset(). See shared_array or std::vector if your needs are more
complex.
template< typename T , typename Deleter = array_deleter<T> > class
scoped_array : public scoped_ptr<T,Deleter>
{
public:
        typedef T element_type;
        explicit scoped_array( T* p=0,const Deleter& d = array_deleter<T>())
throw() : scoped_ptr<T,Deleter>(p,d) {}
        ~scoped_array() { reset(); }
        T& operator[](size_t i) const throw() { return ptr()[i]; }
}; // scoped_array

// shared_ptr -------------------------------------------------------
-------//
// An enhanced relative of scoped_ptr with reference counted copy
semantics.
// The object pointed to is deleted when the last shared_ptr pointing
to it
// is destroyed or reset.
template< typename T , typename Deleter = deleter<T> > class shared_ptr
: public base_ptr< T , Deleter >
{
public:

        typedef T element_type;

        explicit shared_ptr(T* p =0,const Deleter& d = deleter<T>()) :
base_ptr<T,Deleter>(p,d) {
                 try {
                        pn = new long(1);
                 } // fix: prevent leak if new throws
                 catch (...) {
                         del();
                         throw;
                 }
     }

        ~shared_ptr() { dispose(); }
    
        shared_ptr(const shared_ptr& r) throw() : base_ptr<T,Deleter>(r)
        {
         ++*(pn = r.pn);
        }

        shared_ptr(std::auto_ptr<T>& r,const Deleter& d = deleter<T>()) :
base_ptr<T,Deleter>(NULL,d) {
         pn = new long(1); // may throw
         ptr() = r.release(); // fix: moved here to stop leak if new
throws
        }

        shared_ptr& operator=(const shared_ptr& r) {
         share(r.get(),r.pn);
                 return *this;
        }

        shared_ptr& operator=(std::auto_ptr<T>& r) {
         // code choice driven by guarantee of "no effect if new throws"
         if (*pn == 1)
                 {
                         deleter(px);
                 }
                 else
                 {
                        // allocate new reference counter
                        long * tmp = new long(1); // may throw
                        --*pn; // only decrement once danger of new throwing is past
                        pn = tmp;
                 } // allocate new reference counter
                 ptr() = r.release(); // fix: moved here so doesn't leak if new
throws
                 return *this;
        }

        void reset(T* p=0) {
                if ( ptr() == p )
                        return; // fix: self-assignment safe
                if (--*pn == 0)
                {
                        del();
                }
                else
                { // allocate new reference counter
                        try
                        {
                                pn = new long;
                        } // fix: prevent leak if new throws
                        catch (...)
                        {
                          ++*pn; // undo effect of --*pn above to meet effects guarantee
                          del(p);
                          throw;
                        } // catch
                } // allocate new reference counter
                *pn = 1;
                ptr() = p;
        } // reset

   long use_count() const throw(){ return *pn; }
   bool unique() const throw() { return *pn == 1; }

   void swap(shared_ptr<T>& other) throw()
       { std::swap(ptr(),other.ptr()); std::swap(pn,other.pn); }

protected:

        long* pn; // Counter
        
        void dispose() { if (--*pn == 0) { del(); delete pn; } }
        
        void share(T* rpx, long* rpn) {
                if (pn != rpn) {
                        dispose();
                        ptr() = rpx;
                        ++*(pn = rpn);
                }
        } // share
}; // shared_ptr

template <class T,class Deleter = array_deleter<T> > class shared_array
: public shared_ptr<T,Deleter>
{
public:
        explicit shared_array(T* p =0,const Deleter& d = array_deleter<T>()) :
shared_ptr<T,Deleter>(p,d) {};
        ~shared_array() { dispose(); }
        T& operator[](size_t i) const throw() { return ptr()[i]; }
};

Well everything is fine and we've solved the problems we had at hand
without
adding any large overhead thanks to the empty base class optimisation
pointed
out by Howard. But if the deleter was to behave like a unary function
we
wanted to be able to send in a raw function pointer as well. And thats
what the second specialization in Howards mail is for. A partial
specialization for function pointers. And now were up to Howards mail
with the Allocator or simple allocator solution which seems to be a nice
one.

Hope this clarifies things ;).

Have a nice day
/Michel


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