Умный указатель C++03 с free()

У меня есть указатель на символ, который мне нужно передать в собственность, и я бы предпочел не обрабатывать его жизненный цикл самостоятельно, если это возможно. Память выделяется с помощью malloc (здесь нет выбора).

Поэтому я ищу что-то вроде unique_ptr в C++11, который управляет владением и позволяет настраивать Deleter быть обеспеченным.

Как видно из названия, у меня нет доступа к функциям C++11. auto_ptr насколько я знаю, не ходячий delete скорее, чем free,

В этом случае есть подходящий умный указатель, или мне придется самостоятельно управлять освобождением памяти?

1 ответ

В качестве альтернативы написанию этого документа вы можете ввести зависимость от Boost и просто использовать boost::shared_ptr,

Но для сравнения, здесь приведен минимальный код C++11 и более поздних версий для malloc/free основанный указатель передачи собственности, как 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; }
};

Как вы можете видеть, это не так много кода в C++11.

Отказ от ответственности: приведенный выше код не был замечен компилятором.


В C++03 основная проблема заключается в том, как сделать возможным возвращение интеллектуального указателя из функции, не допуская общего построения копирования, что может привести к хаосу.

Решение, используемое std::auto_ptr должен был привлечь класс-носитель указателя-посредника с неявными преобразованиями. Это было сложно. Я помню, как сталкивался с множеством идиосинкразий в реализации Visual C++ std::auto_ptr, когда я написал учебник по указателям (на который ссылается Википедия).

Код ниже, надеюсь, действительный C++03 (протестирован с g++ -std=c++03), вместо этого основан на том, что программист явно указывает, где требуется операция перемещения, вызывая as_movable функция-член. Оно использует volatile как вид тега, чтобы гарантировать, что только движущийся конструктор может уместиться, когда результат as_movable используется в качестве аргумента конструктора. Идея использования volatile как тег в C++03, хотя и в совершенно ином контексте, когда-то был представлен Андреем Александреску; возможно, другие до него, но, насколько я помню, его использование было, где я впервые столкнулся с этой идеей.

Операторы размещения и освобождения размещения, operator new а также operator delete, определены для исключительной безопасности. В частности, размещение operator delete определенный здесь, вызывается только неявно, new-expression, когда конструктор соответствующего типа указывает на ошибку, вызывая исключение. Затем память освобождается с помощью этого оператора, прежде чем исключение будет переброшено.

#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" );
}

Выход:

Вызов бара ()...
Привет из Бла-ленд!:)<Уничтожить>Вернулся из бара ().

Отказ от ответственности: я не написал ни одного модульного теста для приведенного выше кода, на самом деле единственное тестирование - это то, что показано выше, это работает. Тестирование различных случаев, для которых нужно, чтобы это работало, ИМХО необходимо для использования этого в рабочем коде.

Другие вопросы по тегам