I'm revisting an idea that was brought up a few days ago about a non-slicing smart-pointer.
It occurred to me that this might be a pretty useful addition to the STL, and so I was hoping that some of you here might be interested in helping to develop it into something that could eventually be submitted as a recommendation to the C++ standards commitee?
At any rate, all comments and suggestions are welcome. Here is the current implementation:
Code:
#include <memory > // for std::allocator
#include <algorithm > // for std::swap
/*
A simple "non-slicing" memory manager.
Note: NOT to be used to manage dynamically-allocated
arrays (use std::vector or similar for arrays)
*/
template < typename Type, typename Allocator = std::allocator< Type > >
class value_ptr
{
protected:
class internal_allocator_base
{
public:
virtual Type* clone( Type const* ) const = 0;
virtual void destroy( Type* ) const = 0;
};
template < typename Derived >
class internal_allocator : public internal_allocator_base
{
public:
virtual Type* clone( Type const* ptr ) const
{
if( ptr == 0 )
return 0;
Derived*
val = allocator.allocate( 1 );
try
{
allocator.construct( val, *static_cast< Derived const* >( ptr ) );
}
catch( ... )
{
allocator.deallocate( val, 1 );
throw;
}
return val;
}
virtual void destroy( Type* ptr ) const
{
if( ptr != 0 )
{
Derived*
tmp = static_cast< Derived* >( ptr );
try
{
allocator.destroy( tmp );
}
catch( ... )
{
allocator.deallocate( tmp, 1 );
throw;
}
allocator.deallocate( tmp, 1 );
}
}
static internal_allocator
global;
static typename Allocator::template rebind< Derived >::other
allocator;
};
public:
template < typename Derived >
value_ptr( Derived* ptr )
{
init( );
reset( ptr );
}
value_ptr( Type* ptr = 0 )
{
init( );
reset( ptr );
}
value_ptr( value_ptr const& rhs )
{
init( );
reset( rhs );
}
template < typename Derived >
value_ptr& reset( Derived* ptr )
{
internal_reset( ptr );
m_iab_ = &internal_allocator< Derived >::global;
return *this;
}
template < typename Derived >
inline value_ptr& operator = ( Derived* ptr )
{
return reset( ptr );
}
inline value_ptr& reset( Type* ptr = 0 )
{
return reset< Type >( ptr );
}
inline value_ptr& operator = ( Type* ptr )
{
return reset( ptr );
}
value_ptr& reset( value_ptr const& rhs )
{
internal_reset( rhs.m_iab_->clone( rhs.m_ptr_ ) );
m_iab_ = rhs.m_iab_;
return *this;
}
inline value_ptr& operator = ( value_ptr const& rhs )
{
return reset( rhs );
}
template < typename Derived >
value_ptr& allocate( Derived const& val = Derived( ) )
{
return reset
(
static_cast< Derived* >
(
internal_allocator< Derived >::global.clone( &val )
)
);
}
inline value_ptr& allocate( void )
{
return allocate< Type >( );
}
friend void swap( value_ptr& lhs, value_ptr& rhs )
{
std::swap( lhs.m_ptr_, rhs.m_ptr_ );
std::swap( lhs.m_iab_, rhs.m_iab_ );
}
#ifdef VALUE_PTR_NO_IMPLICIT_CONVERSION
inline Type& operator * ( void )
{
return *m_ptr_;
}
inline Type const& operator * ( void ) const
{
return *m_ptr_;
}
#else
inline operator Type* ( void )
{
return m_ptr_;
}
inline operator Type const* ( void ) const
{
return m_ptr_;
}
#endif
inline Type* operator -> ( void )
{
return m_ptr_;
}
inline Type const* operator -> ( void ) const
{
return m_ptr_;
}
virtual ~value_ptr( void )
{
reset( );
}
protected:
void init( void )
{
m_ptr_ = 0;
m_iab_ = &internal_allocator< Type >::global;
}
void internal_reset( Type* ptr )
{
/*
The temporary ensures that if the 'current'
data's destructor throws an exception,
the 'new' memory will still be cleaned up.
*/
Type*
saved = m_ptr_;
m_ptr_ = ptr;
m_iab_->destroy( saved );
}
Type*
m_ptr_;
internal_allocator_base*
m_iab_;
};
template < typename Type, typename Allocator >
template < typename Derived >
typename value_ptr< Type, Allocator >::template internal_allocator< Derived >
value_ptr< Type, Allocator >::internal_allocator< Derived >::global
= typename value_ptr< Type, Allocator >::template internal_allocator< Derived >( );
template < typename Type, typename Allocator >
template < typename Derived >
typename Allocator::template rebind< Derived >::other
value_ptr< Type, Allocator >::internal_allocator< Derived >::allocator
= typename Allocator::template rebind< Derived >::other( );
And a simple test program:
Code:
#include <iostream>
class base
{
public:
void print( char const* msg )
{
std::cout
<< reinterpret_cast< int* >( this )
<< " : " << msg << std::endl;
}
base( void )
{
print( "base::base( )" );
}
base( base const& )
{
print( "base::base( base const& )" );
}
virtual void test( void )
{
print( "base::test( )" );
}
~base( void )
{
print( "base::~base( )" );
}
};
class derived : public base
{
public:
derived( void )
{
print( "derived::derived( )" );
}
derived( derived const& rhs )
: base( rhs )
{
print( "derived::derived( derived const& )" );
}
virtual void test( void )
{
print( "derived::test( )" );
}
~derived( void )
{
print( "derived::~derived( )" );
}
};
int main( void )
{
value_ptr< base >
mb1 = new derived( ),
mb2 = mb1;
mb2->test( );
return 0;
}
One thing I couldn't decide on was whether or not to give each instance a copy of it's allocator. It would certainly be a little more flexible (and more in line with other STL objects), but it just seemed so wasteful for a single object, so I've left it out for now.