Stefano Borini
Stefano Borini

Reputation: 143925

Underlying design of boost shared_ptr

I am trying to understand the underlying design of boost shared_ptr class. I want to "port" it to fortran (don't ask). One thing I understand is that the reference count is held by a shared_count class. This prompts me a question. I haven't used C++ since a long time, and never used boost.

Suppose I allocate a single instance of a class X, then pass it to two different shared_ptr instances. From what I understand, each shared_ptr instance does not know anything about the other, hence both shared_ptr instances are referring to the same X instance, while keeping a refcount of 1. if one shared_ptr goes out of scope while the other doesn't, the X object will be deleted (as the refcount drops to zero) and the remaining shared_ptr will have a dangling pointer. In order to keep the shared_ptr refcount, you have to create a shared_ptr from another shared_ptr.

Am I right ? If not, how can boost keep track of which shared_ptrs are referencing a class that knows nothing about the fact that is being referenced through shared_ptrs ?

Upvotes: 4

Views: 716

Answers (2)

Zeta
Zeta

Reputation: 105955

Basically you're right. Your example will result in dangling pointer (note that there are some exceptions if you use boost::enable_shared_from_this as base class).

Explanation

Problem

boost:shared_ptr and std::shared_ptr share the same idea: create a smart pointer with a reference count from a raw pointer. However, they also share the same problem that all smart pointer have: if you use the raw pointer in another smart pointer which isn't associated to your other smart pointer, you'll end with dangling pointers and multiple calls of delete:

int * ptr = new int;
{
    std::shared_ptr<int> shared1(ptr); // initialise a new ref_count = 1
    {
        std::shared_ptr<int> shared2(ptr);  // initialise a new ref_count = 1
    } // first call of delete, since shared2.use_count() == 0
} // second call of delete, since shared1.use_count() == 0. ooops

"Solution"

After you created your first smart pointer S from a raw pointer p to an object O you should only use copy constructors with S, not with p, as long as O isn't a derivate from std::enable_shared_from_this. boost has a somewhat equivalent of this, but mixing raw pointer and smart pointer is still a bad idea. Even better - don't use raw pointer if you work with smart pointer:

std::shared_ptr<int> ptr(new int);
{
    std::shared_ptr<int> shared1(ptr); // ptr.use_count() == 2
    {
        std::shared_ptr<int> shared2(ptr);  // ptr.use_count()  = 3
    } // ptr.use_count()  = 2
}  // ptr.use_count()  = 1

Even better, don't allocate the memory yourself but use std::make_shared or boost:make_shared:

std::shared_ptr<int> ptr = std::make_shared<int>();
{
    std::shared_ptr<int> shared1(ptr); // ptr.use_count() == 2
    {
        std::shared_ptr<int> shared2(ptr);  // ptr.use_count() == 3
    } // ptr.use_count() == 2
}  // ptr.use_count() == 1

Possible implementation

The following implementation is very crude compared to the std::shared_ptr, as it doesn't support std::weak_ptr and std::enable_shared_from_this. However, it should give you an overview how to handle a shared pointer:

//!\brief Base clase for reference counter
class reference_base{
    reference_base(const reference_base&);                            // not copyable
    reference_base& operator=(const reference_base &){return *this;}// not assignable    

protected:
    size_t ref_count; //!< reference counter
    virtual void dispose() = 0; //!< pure virtual
public:    
    //! initialize with a single reference count
    reference_base() : ref_count(1){}

    //! returns the current count of references
    size_t use_count() const{
        return ref_count;
    }

    //! increases the current count of references
    void increase(){
        ref_count++;
    }

    //! decreases the current count of references and dispose if the counter drops to zero
    void decrease(){
        if(--ref_count == 0)
            dispose();
    }
};

//! \brief Specialized version for pointer
template <class T>
class reference_base_ptr : public reference_base{
    typedef T* pointer_type;
protected:
    //! uses delete to deallocate memory
    virtual void dispose(){
        delete ptr;
        ptr = 0;
    }
public:
    reference_base_ptr(T * ptr) : ptr(ptr){}
    pointer_type ptr;
};

//! \brief Specialized version for arrays
template <class T>
class reference_base_range : public reference_base{
    typedef T* pointer_type;

protected:
    virtual void dispose(){
        delete[] ptr;
        ptr = 0;
    }
public:
    reference_base_range(T * ptr) : ptr(ptr){}
    pointer_type ptr;
};

/***********************************************************/

//! base class for shared memory
template <class T, class reference_base_type>
class shared_memory{
    public:
        typedef T element_type;

        //! Standard constructor, points to null
        shared_memory() : reference_counter(new reference_base_type(0)){}

        //! Constructs the shared_memroy and creates a new reference_base
        template<class Y> shared_memory(Y * ptr){
            try{
                reference_counter = new reference_base_type(ptr);
            }catch(std::bad_alloc &e){
                delete ptr;
                throw;
            }
        }
        //! Copies the shared_memory and increases the reference count
        shared_memory(const shared_memory & o) throw() : reference_counter(o.reference_counter){
            o.reference_counter->increase();
        }

        //! Copies the shared_memory of another pointer type and increases the reference count.
        //! Needs the same reference_base_type
        template<class Y> 
        shared_memory(const shared_memory<Y,reference_base_type> & o) throw() : reference_counter(o.reference_counter){
            reference_counter->increase();
        }

        //! Destroys the shared_memory object and deletes the reference_counter if this was the last
        //! reference.        
        ~shared_memory(){
            reference_counter->decrease();
            if(reference_counter->use_count() == 0)
                delete reference_counter;
        }

        //! Returns the number of references
        size_t use_count() const{
            return reference_counter->use_count();
        }

        //! Returns a pointer to the refered memory
        T * get() const{
            return reference_counter->ptr;
        }

        //! Checks whether this object is unique
        bool unique() const{
            return use_count() == 1;
        }        

        //! Checks whehter this object is valid
        operator bool() const{
            return get() != 0;
        }

        //! Checks doesn't reference anythign
        bool empty() const{
            return get() == 0;
        }

        //! Assignment operator for derived classes
        template<class Y> 
        shared_memory& operator=(const shared_memory<Y,reference_base_type> & o){
            shared_memory<Y,reference_base_type> tmp(o);
            swap(tmp);
        }

        //! Assignment operator
        shared_memory& operator=(const shared_memory & o){
            shared_memory tmp(o);
            swap(tmp);
            return *this;
        }

        /** resets the ptr to NULL. If this was the last shared_memory object
        *   owning the referenced object, the object gets deleted.
        *   \sa ~shared_memory
        */
        void reset(){
            shared_memory tmp;
            swap(tmp);
        }

        /** releases the old object and takes a new one
        */
        template <class Y>
        void reset(Y * ptr){
            shared_memory tmp(ptr);
            swap(tmp);
        }        

        /** swaps the owned objects of two shared_memory objects.
        */
        void swap(shared_memory & r){
            reference_base_type * tmp = reference_counter;
            reference_counter = r.reference_counter;
            r.reference_counter = tmp;
        }

    protected:        
        reference_base_type * reference_counter;    //!< Actually reference counter and raw pointer
};

/***********************************************************/

//! ptr (single object) specialization
template <class T>
class shared_ptr : public shared_memory<T,reference_base_ptr<T> >{
    typedef reference_base_ptr<T> reference_counter_type;
    typedef shared_memory<T,reference_counter_type> super;
    typedef T element_type;
public:
    shared_ptr(){}
    template<class Y> shared_ptr(Y * ptr){
        try{
            super::reference_counter = new reference_counter_type(ptr);
        }catch(std::bad_alloc &e){
            //couldn't allocated memory for reference counter
            delete ptr; // prevent memory leak
            throw bad_alloc();
        }
    }
    element_type & operator*() const{
        return *(super::reference_counter->ptr);
    }
    element_type * operator->() const{
        return super::reference_counter->ptr;
    }
};

/***********************************************************/

//! array (range) specialization
template <class T>
class shared_array : public shared_memory<T,reference_base_range<T> >{
    typedef reference_base_range<T> reference_counter_type;
    typedef shared_memory<T,reference_counter_type> super;
    typedef T element_type;

public:
    shared_array(){}
    template<class Y> shared_array(Y * ptr){
        try{
            super::reference_counter = new reference_counter_type(ptr);
        }catch(std::bad_alloc &e){
            delete[] ptr;
            throw bad_alloc();
        }
    }
    element_type & operator[](int i) const{
        return *(super::reference_counter->ptr + i);
    }
};

See also:

Upvotes: 7

MSalters
MSalters

Reputation: 180235

It's the current implementation of boost::shared_ptr<X>, but others can be thought of which do not share this disadvantage. E.g. a static std::unordered_map<X*, int> std::shared_ptr<X>::share_count can be used to keep track of the amount of shared_ptr<X> pointers to each X. However, the downside of this is a far larger overhead than a simple share count.

Upvotes: 0

Related Questions