I have been working on replacing raw pointers with reference-counted pointers that expose only a const version of the underlying one. My objective is to reduce memory usage (and time spent unnecessarily constructing and destructing complex objects) without putting myself in a situation where any code has access to memory that it does not own. I am aware of the circular reference problem with reference counting, but my code should never create such a situation.
Requiring const-ness works because I am using a system in which classes generally expose no non-const members, and rather than altering an object you must call a method on it that returns a new object that results from the alteration. This is probably a design pattern, but I do not know it by name.
My problem has come when I have a method that returns pointer to an object of its type, which is sometimes itself. Previously it looked something like this:
Foo * Foo::GetAfterModification( const Modification & mod ) const
{
if( ChangesAnything( mod ) )
{
Foo * asdf = new Foo;
asdf.DoModification( mod );
return asdf;
}
else
return this;
}
I cannot see a good way to make this return a smart pointer. The naive approach would be something like return CRcPtr< Foo >( this )
, but this breaks the ownership semantics because what I am returning and whomever previously owned the object now each think they have ownership but do not know about each other. The only safe thing to do would be return CRcPtr< Foo >( new Foo( *this ) )
, but that defeats my intention of restricting unnecessary memory use.
Is there some way to safely return a smart pointer without allocating any additional memory? My suspicion is that there is not. If there were, how would it work if the object had been allocated on the stack? This question seems related, but is not the same because he can just make his functions take raw pointers as parameters and because he is using the boost library.
For reference, my home-rolled smart pointer implementation is below. I am sure it could be more robust, but it is more portable than depending on boost or tr1 being available everywhere, and until this issue it has worked well for me.
template <class T>
class CRcPtr
{
public:
explicit CRcPtr( T * p_pBaldPtr )
{
m_pInternal = p_pBaldPtr;
m_iCount = new unsigned short( 1 );
}
CRcPtr( const CRcPtr & p_Other )
{ Acquire( p_Other ); }
template <class U>
explicit CRcPtr( const CRcPtr< U > & p_It )
{
m_pInternal = dynamic_cast< T * >( p_It.m_pInternal );
if( m_pInternal )
{
m_iCount = p_It.m_iCount;
(*m_iCount)++;
}
else
m_iCount = new unsigned short( 1 );
}
~CRcPtr()
{ Release(); }
CRcPtr & operator=( const CRcPtr & p_Other )
{
Release();
Acquire( p_Other );
}
const T & operator*() const
{ return *m_pInternal; }
const T * operator->() const
{ return m_pInternal; }
const T * get() const
{ return m_pInternal; }
private:
void Release()
{
(*m_iCount)--;
if( *m_iCount == 0 )
{
delete m_pInternal;
delete m_iCount;
m_pInternal = 0;
m_iCount = 0;
}
}
void Acquire( const CRcPtr & p_Other )
{
m_pInternal = p_Other.m_pInternal;
m_iCount = p_Other.m_iCount;
(*m_iCount)++;
}
template <class U>
friend class CRcPtr;
T * m_pInternal;
unsigned short * m_iCount;
};
template <class U, class T>
CRcPtr< U > ref_cast( const CRcPtr< T > & p_It )
{ return CRcPtr< U >( p_It ); }
Edit: Thanks for the replies. I was hoping to avoid using boost or tr1, but I recognize that not using well tested libraries is generally unwise.
-
Take a look at the source for Boost shared pointers. If you derive a class from
enable_shared_from_this<T>
, you can then call theshared_from_this()
member function to do that sort of thing.Orion Edwards : +1. I've used boost::shared_ptr and shared_from_this before and it works very well -
As far as semantics go, the great thing about immutability is that you can safely share data between objects, threads, and not have to worry about people changing values you depend on. You shouldn't need to worry about ownership so long as your refcounting scheme is correct (I didn't read yours, but use Boost anyway).
-
As eluded to by Ferruccio, you need some form of shared pointer. Ie, one that can be shared (safely!) between multiple objects. One way to make this work is to derive all your objects (at least those that are intended for use with the shared pointer) from a class that implements the actual reference count. That way, the actual pointer itself carries the current count with it, and you share the pointer between as many different objects as you like.
What you currently have is very similar to std::auto_ptr and you are bumping into the same issues with ownership. Google it and you should find some useful info.
Be aware that there are additional complications with shared pointers: in particular in multi-threaded environments your reference counting must be atomic and self assignment must be handled to avoid incrementing the internal count such that the object is never destroyed.
Again google is your friend here. Just look for info on shared pointers in C++ and you'll find tonnes of info.
0 comments:
Post a Comment