Using const_pointer_cast<T>(..)
This is a quick demonstration of const_pointer_cast<T>(..).
# include <boost/shared_ptr.hpp>
using boost::shared_ptr;
using boost::const_pointer_cast;
struct test_type { };
// Make an object and give it to a shared_ptr.
shared_ptr< test_type >
sp_non_const(
new test_type);
// Copy the shared_ptr to another, except the
// new shared_ptr holds a pointer to a const.
// This copy can be an implicit cast.
shared_ptr< test_type const >
sp_const(
sp_non_const);
// Copy the shared_ptr to a const to another
// shared_ptr, one that holds a non-const
// pointer. This is not an implicit cast.
// This is what const_pointer_cast is for.
shared_ptr< test_type >
sp_non_const2(
const_pointer_cast< test_type >( sp_const));
And there you have it. const_pointer_cast<T>(..) is used to cast away the const (or volatile) buried inside a smart pointer. It works with shared_ptr<T>s, intrusive_ptr<T>s, and raw pointers. Without it you would have to resort to reinterpret_cast<T>(..) to cast away const buried inside a shared_ptr<T> unless it provided ->shared_from_this().
Managed memory and shared_ptr<T>s
In my last post I talk about how Boost implements shared_ptr<T>s with an intermediate object between the shared_ptr and the target. The intermediate object serves a few purposes.
It holds the reference count
Otherwise it would have to forgo the reference count (like a true garbage collector), or keep the reference count in a separate map/dictionary, or force the target object to make room for the reference count. The intermediate object lets shared_ptr<T> provide an efficient implementation that works with any target class.
It enables weak pointers
Weak pointers of class weak_ptr<T> point to the same intermediate object as shared_ptr<T>. There are ways to implement weak pointers without the intermediate object, but they are questionable. An intrusive implementation would require the target to provide a pointer from the target object to a linked list of weak pointers, which could then be cleared before the target was deleted. But this involves a lot of mutex-style locking and can be expensive. The Boost implementation is very clever about avoiding these kinds of locks, instead using interlocking increment/decrement.
Other solutions, involving delayed delete and property maps/dictionaries, are even less attractive.
It enables compaction
Some garbage collecting systems always use intermediate objects. The intermediate objects all together are usually called the object table. One of the big advantages is that when the garbage collector moves a target object it only has to update a single pointer, the one in the object table. The object table entries (the intermediate objects) do not move, but fragmentation isn’t much of a problem because they are small, kept in an array, and reused often.
It is easy to see how you might set up something similar in C++. It would provide compaction and reduce memory fragmentation even without full garbage collection. You’d allocate managed objects from a special heap, and when the heap got too fragmented you’d create another heap, copy the objects across, update all the pointers in the object table, and destroy the first heap. This is especially attractive if you are coding in a no-side-effects functional style.
(It wouldn’t work with Boost 1_37 shared_ptr<T>s though, since they not only store the target pointer in the intermediate object (where it is used for delete) but also in the shared_ptr<T> and weak_ptr<T> objects themselves. They do this because casting and aliasing require that the target pointer and the deleted pointer sometimes be different.)
Notes on shared_ptr< T >
The C++ shared_ptr< T > is a strong shared-ownership pointer that keeps the target object (the referent) alive as long as it points to it. The last shared_ptr< T > to drop an object also deletes it.
The C++ shared_ptr< T > uses reference counting to know how many other shared_ptr< T >s are pointing at the object. The reference count is not stored in the target object. Instead the shared_ptr< T > creates an intermediate object which holds the reference count and a pointer to the target.
This intermediate object has to be allocated, so when you alloc a new object and give it to a shared_ptr< T >, you are also allocating a new intermediate object. Creating 100 new shared objects also creates 100 new intermediate objects.
Many shared_ptr< T >s and weak_ptr< T >s can point to one intermediate object, but only one intermediate object should point to the target object. The pointer in the intermediate object has sole ownership of the target object and never lets it go until it deletes it. In this way the pointer in the intermediate object is a lot like a scoped_ptr< T >.
There should never be more than one intermediate object for any given target. This is important to remember because it is easy to get wrong. All you have to do is create a shared_ptr< T > twice from the same raw pointer.
# include <boost/shared_ptr.hpp>
using boost::shared_ptr;
class my_class1 { };
// Creates a my_class1 object.
my_class * p_a = new my_class1;
// Creates (allocs) a new intermediate sharing and
// ref-counting object. This object sits between
// sp1_a and p_a.
shared_ptr< my_class1 > sp1_a( p_a);
// Copy the shared_ptr to another shared_ptr.
// Uses the same intermediate object in both shared_ptrs.
shared_ptr< my_class1 > sp1_b( sp1_a);
// Double-delete bug:
// Creating a shared_ptr from a raw pointer causes
// another intermediate object to be created. This
// is only OK the first time, and illegal now since
// an intermediate object has already been created
// for p_a.
// Even though this is illegal it still compiles and
// runs. But p_a gets deleted twice since it is now
// "owned" by two intermediate objects.
shared_ptr< my_class1 > sp1_c( p_a); /* BUG! */
After you create the first shared_ptr< T > for an object, you should create all future shared and weak pointers from existing shared/weak pointers.
But there is a way around this. The base class boost::enable_shared_from_this< T > keeps a private weak_ptr< T > that points back to itself. It then uses that weak_ptr< T > to make shared_ptr< T >s through the method shared_from_this( ).
# include <boost/shared_ptr.hpp>
# include <boost/enable_shared_from_this.hpp>
using boost::shared_ptr;
using boost::enable_shared_from_this;
class my_class2
: public enable_shared_from_this< my_class2 >
{ };
// These are all good.
my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_a( p_2);
shared_ptr< my_class2 > sp2_b( sp2_a);
shared_ptr< my_class2 > sp2_c = p_2->shared_from_this( );
But boost::enable_shared_from_this< T > doesn’t solve all problems. It doesn’t stop double deletes.
my_class2 * p_2 = new my_class2; shared_ptr< my_class2 > sp2_a( p_2); // Illegal even with enable_shared_from_this<T> base. // The object will be deleted twice. shared_ptr< my_class2 > sp2_b( p_2);
In this case the boost implementation (1_37_0) calls the _internal_assign(..) method from weak_ptr.hpp.
// from weak_ptr.hpp in boost 1_37_0
void _internal_assign(T * px2, boost::detail::shared_count const & pn2)
{
px = px2;
pn = pn2;
}
I think this code could use an assert, which would detect a situation that eventually leads to a double delete. The code would then look like this:
// from weak_ptr.hpp in boost 1_37_0 with added assert
void _internal_assign(T * px2, boost::detail::shared_count const & pn2)
{
// This is only used to initialize the weak pointer in the
// class enable_shared_from_this<T>. If the following
// assert fails then the referent has already been given
// over to a shared_ptr.
BOOST_ASSERT( (px == 0) && (pn.use_count() == 0));
px = px2;
pn = pn2;
}
If you have a raw pointer of a enable_shared_from_this< T > subtype you might think you can just always call ->shared_from_this( ) to get a shared pointer, but that doesn’t work either. You have to always assign the raw pointer to a shared_ptr< T > first, before you can call ->shared_from_this( ).
// Throws boost::bad_weak_ptr. // Cannot call ->shared_from_this( ) until after the first // time it is assigned to a shared_ptr<..>. my_class2 * p_2 = new my_class2; shared_ptr< my_class2 > sp2_e( p_2->shared_from_this( ));
The shared_ptr< T > raw-pointer constructor could easily be re-written to avoid these double-delete problems for subtypes of enable_shared_from_this< T > by figuring out if the internal weak_ptr< T > is initialized or not before deciding what to do. You can even do it yourself with something like this.
template< typename T >
shared_ptr< T >
get_shared_ptr_for( T * p_obj)
{
try {
return p_obj->shared_from_this( );
}
catch ( boost::bad_weak_ptr ) {
return shared_ptr< T >( p_obj);
}
}
...
my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_f( get_shared_ptr_for( p_2));
shared_ptr< my_class2 > sp2_g( get_shared_ptr_for( p_2));
BOOST_ASSERT( sp2_f == sp2_g);
Changing the shared_ptr< T >::shared_ptr( T *) constructor to do something similar might be a good idea, but it would not work for classes that did not derive from enable_shared_from_this< T >. The inconsistency between the two types of classes, those with and those without a enable_shared_from_this< T > supertype, would also cause confusion, especially with generic code.
It would be more consistent with intrusive_ptr< T > though, which doesn’t use intermediate sharing/ref-count objects. Or maybe it’s more accurate to say the intermediate object is intrusively part of the target object, so you can never have more than one for any given target. You don’t have to worry about double-deletes with intrusive_ptr< T >s.