0

I have a char pointer that I need to transfer ownership of, and I would rather not have to handle its lifecycle myself if possible. The memory is allocated using malloc (no choice here).

As such I'm looking for something like C++11's unique_ptr, which manages ownership and allows a custom Deleter to be provided.

As the title suggests, I don't have access to C++11 features though. auto_ptr isn't a goer as far as I'm aware, as it calls delete rather than free.

Is there an appropriate smart pointer in this case, or will I have to manage the freeing of the memory myself?

Quentin
  • 62,093
  • 7
  • 131
  • 191
rbennett485
  • 1,907
  • 15
  • 24
  • 1
    I guess you could write your own (it's not that much code) – UnholySheep Jan 10 '17 at 13:35
  • @UnholySheep yep, I'd rather not if there is a suitable alternative out there already though – rbennett485 Jan 10 '17 at 13:36
  • 2
    You might want to read [this old question and its answers](http://stackoverflow.com/q/2953530/440558). Also, considering your requirements it should not be that much work to write your own custom wrapper class for this specific problem. – Some programmer dude Jan 10 '17 at 13:36
  • 1
    Depending on how intelligent the smart pointer needs to be, you could write a struct that accepts your `char*` in a ctor, and then in the destructor, calls free. This would be a lightweight unique pointer. (Of course you'd have to declare copy ctor and copy assignment as private and then not implement them to avoid copying). Ofc you'd also have to replicate the move functionality, probably as a member function that produces the `char*` pointer, and then will ensure no `free` is called in the `dtor` – AndyG Jan 10 '17 at 13:36
  • I guess using [Boost smart pointers with a deleter](http://www.boost.org/doc/libs/1_61_0/libs/smart_ptr/shared_ptr.htm#deleter_constructor) might also be an option? (Though that requires you to add boost as a dependency which you may or may not want to do) – UnholySheep Jan 10 '17 at 13:38
  • 2
    You can use `boost::shared_ptr`. Or you can write your own. – Cheers and hth. - Alf Jan 10 '17 at 14:02

1 Answers1

3

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331