As an alternative to writing it yourself you can introduce a dependency on Boost and just use boost::shared_ptr
.
But for comparison, here’s minimal off-the-cuff C++11 and later code for an malloc
/free
based ownership transfer pointer, like std::unique
:
template< class Type >
class My_ptr
{
private:
Type* p_;
My_ptr( My_ptr const& ) = delete;
operator=( My_ptr const& ) -> My_ptr& = delete;
public:
auto operator->() const
-> Type*
{ return p; }
auto operator*() const
-> Type&
{ return *p; }
~My_ptr() { free( p_ ); }
My_ptr( Type* p )
: p_( p )
{}
My_ptr( My_ptr&& other )
: p_( other.p_ )
{ other.p_ = nullptr; }
};
As you can see it's not much code in C++11.
Disclaimer: the above code has not been seen by compiler.
In C++03 the main problem is how to make it possible to return the smart pointer from a function, without allowing general copy construction, which would wreak havoc.
The solution used by std::auto_ptr
was to involve a mediator pointer carrier class with implicit conversions. This was complex. I remember encountering a great number of idiosyncrasies in Visual C++'s implementation of std::auto_ptr
, when I wrote a (once referenced by Wikipedia) pointer tutorial.
The code below, hopefully valid C++03 (tested with g++ -std=c++03
), is instead based on the programmer explicitly indicating where a move operation is desired, by calling the as_movable
member function. It uses volatile
as a kind of tag, to ensure that only the moving constructor can fit when the result of as_movable
is used as a constructor argument. The idea of using volatile
as a tag in C++03, though in a quite different context, was once introdued by Andrei Alexandrescu; possibly by others before him, but as far as I recall his usage was where I first encountered that idea.
The placement allocation and deallocation operators, operator new
and operator delete
, are defined for exception safety. In particular, the placement operator delete
defined here is only called implicitly, by a new
-expression, when the relevant type's constructor indicates failure by throwing an exception. Then the memory is deallocated, using this operator, before the exception is re-thrown.
#include <exception> // std::terminate
#include <new> // std::bad_alloc
#include <stddef.h> // size_t
#include <stdlib.h> // malloc, free, NULL
#define MY_NEW( type, args ) \
::new type args
#define MY_MALLOC( type, args ) \
::new( my::c_memory_management ) type args
namespace my {
struct C_memory_management {};
C_memory_management const c_memory_management = C_memory_management();
} // namespace my
void*
operator new( size_t const size, my::C_memory_management )
{
void* result = malloc( size );
if( not result ) { throw std::bad_alloc(); }
return result;
}
// This operator is (only) called automatically by a new-expression where the
// constructor for the type, throws. After the call the exception is re-thrown.
void operator delete( void* const p, my::C_memory_management )
{
free( p );
}
#ifdef SUPPORT_ARRAYS
void*
operator new[]( size_t const size, my::C_memory_management const cmm )
{
return operator new( size, cmm );
}
void operator delete[]( void* const p, my::C_memory_management const cmm )
{
operator delete( p, cmm );
}
#endif
namespace my {
template< class Referent >
struct Destruction_via_delete_
{
static void destroy( Referent const* p )
{
try
{
delete p;
}
catch( ... )
{
std::terminate();
}
}
};
template< class Referent >
struct Destruction_via_free_
{
static void destroy( Referent const* p )
{
try
{
p->~Referent();
}
catch( ... )
{
std::terminate();
}
::free( const_cast<Referent*>( p ) );
}
};
template< class Referent >
class Auto_ptr_
{
public:
typedef void Destruction_func( Referent const* );
private:
Auto_ptr_& operator=( Auto_ptr_ const& ); // No copy assignment.
Auto_ptr_( Auto_ptr_ const& ); // No COPYING via copy constructor.
// A non-const argument copy constructor, for moving, is defined below.
Referent* p_;
Destruction_func* destroy_func_;
static void dummy_destroy_func( Referent const* ) {}
public:
Auto_ptr_ volatile&
as_movable()
{ return const_cast<Auto_ptr_ volatile&>( *this ); }
Referent*
release()
{
Referent* result = p_;
p_ = NULL;
return p_;
}
Referent*
operator->() const
{ return p_; }
Referent&
operator*() const
{ return *p_; }
~Auto_ptr_()
{ destroy_func_( p_ ); }
Auto_ptr_()
: p_( NULL )
, destroy_func_( &dummy_destroy_func )
{}
explicit Auto_ptr_(
Referent* const p,
Destruction_func* const destroy_func = &Destruction_via_delete_<Referent>::destroy
)
: p_( p )
, destroy_func_( destroy_func )
{}
explicit Auto_ptr_(
C_memory_management, // tag
Referent* const p
)
: p_( p )
, destroy_func_( &Destruction_via_free_<Referent>::destroy )
{}
// A C++03 emulation of move constructor; allows return of lvalue.
Auto_ptr_( Auto_ptr_ volatile& other )
: p_( other.p_ )
, destroy_func_( other.destroy_func_ )
{
other.p_ = NULL;
other.destroy_func_ = &dummy_destroy_func;
}
};
} // namespace my
#include <stdio.h>
struct Blah
{
char const* hello() const { return "Hello from Blah-land! :)"; }
~Blah() { printf( "<destroy>\n" ); }
Blah() { printf( "<init>\n" ); }
};
my::Auto_ptr_< Blah >
foo()
{
using namespace my;
Auto_ptr_< Blah > p( c_memory_management, MY_MALLOC( Blah,() ) );
return p.as_movable();
}
void bar( my::Auto_ptr_<Blah> const p )
{
printf( "%s\n", p->hello() );
}
int main()
{
my::Auto_ptr_<Blah> p = foo().as_movable();
printf( "Calling bar()...\n" );
bar( p.as_movable() );
printf( "Returned from bar().\n" );
}
Output:
<init>
Calling bar()...
Hello from Blah-land! :)
<destroy>
Returned from bar().
Disclaimer: I've not written any unit test for the above code, indeed the only testing is what's shown above, that that works. Testing the various cases that one wants this to work for is IMHO necessary for use of this in a production code.