Умный указатель 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" );
}
Выход:
Вызов бара ()... Привет из Бла-ленд!:)<Уничтожить>Вернулся из бара ().
Отказ от ответственности: я не написал ни одного модульного теста для приведенного выше кода, на самом деле единственное тестирование - это то, что показано выше, это работает. Тестирование различных случаев, для которых нужно, чтобы это работало, ИМХО необходимо для использования этого в рабочем коде.