Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 1999-11-23 12:24:33


Howard Hinnant wrote on 11/22/99 2:55 PM
>How about a partial specialization on base_ptr for when the Deleter is a
>function pointer?...

I really hated this suggestion. Anyone else?

I think the smart pointers are getting too smart for their own good. I
have a new proposal that pretty much follows from the code Michel André
posted. The basic idea is to disallow ordinary functions as Deleter's,
rename Deleter to Allocator, and adopt something very close to the
std::allocator interface.

Motivations include:

1. Cleaner and more efficient code (guaranteed empty member
optimization).
2. Perhaps these animals could be put to good use in implementing the
std lib.

Two deleter class's to start off with. They implement just a small
portion of the std::allocator interface:

template<typename T>
class single_deleter
{
public:
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;

        template <class U> struct rebind { typedef single_deleter<U> other; };

        void deallocate(pointer p, size_type) {delete p;}
};

template<typename T>
class array_deleter
{
public:
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;

        template <class U> struct rebind { typedef single_deleter<T> other; };

        void deallocate(pointer p, size_type) {delete [] p;}
};

Not sure if all the typedef's are really a good idea.

I am sure that I like the rebind. I'll show why below.

Before you yell about the unused 2nd arg, here me out.

base_ptr encapsulates the empty member optimization, and a few methods
that seem useful for every smart pointer:

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

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

protected:
        base_ptr(T* p = 0, const allocator_type& a = allocator_type()) :
alloc_(a, p) {}

        T*& ptr() { return alloc_.m; }
        T* ptr() const { return alloc_.m; }

        allocator_type& allocator() { return alloc_; }
        const allocator_type& allocator() const { return alloc_; }
private:
        base_opt<Allocator, T*> alloc_;
};

scoped_ptr follows. I've added release() to its interface:

template<typename T , typename Allocator = single_deleter<T> >
class scoped_ptr
        : public base_ptr<T, Allocator>
{
        typedef base_ptr<T, Allocator> base;
public:
        typedef T element_type;
        typedef Allocator allocator_type;

        explicit scoped_ptr(T* p = 0, const allocator_type& a =
allocator_type()) : base(p, a) {}
        ~scoped_ptr() { allocator().deallocate(ptr(), 1); }

        void reset( T* p=0 ) { if ( ptr() != p ) {
allocator().deallocate(ptr(), 1); ptr() = p; } }
        T* release() { T* tmp = ptr(); ptr() = 0; return tmp; }
};

I haven't done shared_ptr, but I think it follows fairly cleanly. What
I've got next is a collection of "stupid pet tricks" that are beginning
to grow on me:

1. A standard implementation of auto_ptr:

template<class X> class auto_ptr;

template <class Y>
struct auto_ptr_ref
{
        auto_ptr<Y>& p_;
        auto_ptr_ref(const auto_ptr<Y>& a) : p_(const_cast<auto_ptr<Y>&>(a)) {}
};

template<class X>
class auto_ptr
        : public scoped_ptr<X>
{
        typedef scoped_ptr<X> base;
public:
        typedef X element_type;

        // lib.auto.ptr.cons construct/copy/destroy:
        explicit auto_ptr(X* p = 0) : base(p) {}
        auto_ptr(auto_ptr& a) : base(a.release()) {}
        template<class Y> auto_ptr(auto_ptr<Y>& a) : base(a.release()) {}
        auto_ptr& operator=(auto_ptr& a) {reset(a.release()); return *this;}
        template<class Y> auto_ptr& operator=(auto_ptr<Y>& a)
{reset(a.release()); return *this;}

        // lib.auto.ptr.conv conversions:
        auto_ptr(auto_ptr_ref<X> r) : base(r.p_.release()) {}
        auto_ptr& operator=(auto_ptr_ref<X> r) {reset(r.p_.release()); return
*this;}
        template<class Y> operator auto_ptr_ref<Y>() {return *this;}
        template<class Y> operator auto_ptr<Y>() {return auto_ptr<Y>(release());}
};

Nice and compact, and AFAIK, standard. I haven't had the opportunity to
test the last two conversion operators though.

2. An extended auto_ptr that can be used with array_deleter:

template<typename X, typename Allocator = single_deleter<X> > class
auto_ptr;

template <typename Y, typename Allocator = single_deleter<Y> >
struct auto_ptr_ref
{
        auto_ptr<Y, Allocator>& p_;
        auto_ptr_ref(const auto_ptr<Y, Allocator>& a) :
p_(const_cast<auto_ptr<Y, Allocator>&>(a)) {}
};

template<typename X, typename Allocator>
class auto_ptr
        : public scoped_ptr<X, Allocator>
{
        typedef scoped_ptr<X, Allocator> base;
public:
        typedef X element_type;

        // lib.auto.ptr.cons construct/copy/destroy:
        explicit auto_ptr(X* p = 0) : base(p) {}
        auto_ptr(auto_ptr& a) : base(a.release()) {}
        template<class Y> auto_ptr(auto_ptr<Y, Allocator::rebind<Y>::other>& a)
: base(a.release()) {}
        auto_ptr& operator=(auto_ptr& a) {reset(a.release()); return *this;}
        template<class Y> auto_ptr& operator=(auto_ptr<Y,
Allocator::rebind<Y>::other>& a) {reset(a.release()); return *this;}

        // lib.auto.ptr.conv conversions:
        auto_ptr(auto_ptr_ref<X, Allocator> r) : base(r.p_.release()) {}
        auto_ptr& operator=(auto_ptr_ref<X, Allocator> r)
{reset(r.p_.release()); return *this;}
        template<class Y> operator auto_ptr_ref<Y,
Allocator::rebind<Y>::other>() {return *this;}
        template<class Y> operator auto_ptr<Y, Allocator::rebind<Y>::other>()
                {return auto_ptr<Y, Allocator::rebind<Y>::other>(release());}
};

This is where the rebind comes in handy in the allocators. Note that
I've messed with the rebind definitions so that single_deleter allows the
appropriate conversions:

                auto_ptr<B> b(new B);
                auto_ptr<B> b2(b);
                b = b2;
                auto_ptr<B> b3(source());
                auto_ptr<A> a(b); // B derives from A
                a = b3;
                b3 = source();
                *b3 = B();
                sink_b(source());
                auto_ptr<A> a2(source()); // Untested
                a2 = source(); // Untested
                sink_a(source()); // Untested

but array_deleter correctly refuses conversions between base and derived:

                auto_ptr<B, array_deleter<B> > b(new B [2]);
                auto_ptr<B, array_deleter<B> > b2(b);
                b = b2;
                auto_ptr<B, array_deleter<B> > b3(array_source());
                b3 = array_source();
                array_sink(array_source());
// auto_ptr<A, array_deleter<A> > a(b3); // Should not compile
// a = b3; // Should not compile

3. A start on a smart pointer that might be useful in implementing
vector or deque. Don't have the details worked out yet though:

template<typename T , typename Allocator>
class allocated_ptr
        : public base_ptr<T, Allocator>
{
        typedef base_ptr<T, Allocator> base;
        typedef typename Allocator::size_type size_type;
public:
        typedef T element_type;
        typedef Allocator allocator_type;

        explicit allocated_ptr(T* p = 0, size_type cap = 0, const
allocator_type& a = allocator_type())
                : base(p, a), capacity_(cap) {}
        ~allocated_ptr() { allocator().deallocate(ptr(), capacity_); }

        void reset(T* p = 0, size_type cap = 0)
                { if ( ptr() != p ) { allocator().deallocate(ptr(), capacity_); ptr() =
p; capacity_ = cap;} }
        T* release() { T* tmp = ptr(); ptr() = 0; capacity_ = 0; return tmp; }
        size_type capacity() const { return capacity_; }
private:
        size_type capacity_;
};

This is just a scoped_ptr with a capacity thrown in that it can hand back
to Allocator::deallocate. Note that scoped_ptr may be useful as is for
use in node based standard containers because it passes "1" in
Allocator::deallocate. Again, haven't actually implemented a standard
container in terms of one of these. Just exploring the possibility.

-Howard


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