Reputation: 143925
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
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).
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
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
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:
std::shared_ptr
boost::shared_ptr
, especially the section Best PracticesUpvotes: 7
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