/*!
	\file
	\brief Работа с одиночными объектами.
*/

#ifndef _AUTO_PTR_3__OBJ_PTR_HPP_
#define _AUTO_PTR_3__OBJ_PTR_HPP_

namespace auto_ptr_3 {

/*
** obj_ptr_t
*/

//! Класс для автоматического уничтожения динамически
//! созданных одиночных объектов.
template< class T >
class	obj_ptr_t {
	protected :
		//! Указатель на контролируемый объект.
		//! Может содержать 0, если obj_ptr_t в данный момент
		//! ничего не контролирует.
		T *	m_p;

	public :
		typedef T type_t;

		//! Конструктор по умолчанию.
		obj_ptr_t() :
			m_p( 0 ) {
		}

		/*! Конструктор с инициализацией указателя на контролируемый
			объект.
			\param ptr Указатель на контролируемый объект.
		*/
		obj_ptr_t( T * ptr ) :
			m_p( ptr ) {
		}

		/*!
			Деструктор автоматически удаляет контролируемый объект,
			если указатель на него не равен 0.
		*/
		~obj_ptr_t() {
			if( m_p )
				delete m_p;
		}

		/*! Получить доступ к указателю на хранимый объект.
			Может возвращать 0, если контролируемого объекта нет.
		*/
		T *
		query() {
			return m_p;
		}

		/*! Получить доступ к указателю на хранимый объект.
			Может возвращать 0, если контролируемого объекта нет.
		*/
		const T *
		query() const {
			return m_p;
		}

		/*! Установить указатель на хранимый объект.
			Предыдущий хранимый объект (если был) не уничтожается.
		*/
		void
		set( T * ptr ) {
			m_p = ptr;
		}

		/*! Указать отсутствие хранимого объекта.
			Предыдущий хранимый объект (если был) не уничтожается.
		*/
		void
		clear() {
			m_p = 0;
		}

		/*! Уничтожить хранимый объект, если таковой есть.
		*/
		void
		destroy() {
			if( m_p ) {
				T * ptr = m_p;
				m_p = 0;

				delete ptr;
			}
		}

		/*! Поменять значение двух объектов obj_ptr_t.
		*/
		void
		swap( obj_ptr_t< T > & o ) {
			if( &o != this ) {
				T * ptr = m_p;

				m_p = o.m_p;

				o.m_p = ptr;
			}
		}

		//
		// Расширения версии 3
		//

		/*! Получить доступ к хранимому объекту.
			\attention Результат не определен, если нет
				контролируемого объекта.
		*/
		T &
		operator*() {
			return *m_p;
		}

		/*! Получить доступ к хранимому объекту.
			\attention Результат не определен, если нет
				контролируемого объекта.
		*/
		const T &
		operator*() const {
			return *m_p;
		}

		/*! Получить доступ к хранимому объекту.
			\attention Результат не определен, если нет
				контролируемого объекта.
		*/
		T *
		operator->() {
			return m_p;
		}

		/*! Получить доступ к хранимому объекту.
			\attention Результат не определен, если нет
				контролируемого объекта.
		*/
		const T *
		operator->() const {
			return m_p;
		}

		//
		// Обеспечение похожести на std::auto_ptr
		//

		/*! Получить доступ к указателю на хранимый объект.
			Может возвращать 0, если контролируемого объекта нет.

			\note Введен для похожести на std::auto_ptr
		*/
		T *
		get() const {
			return m_p;
		}

		/*! Возвращает указатель на хранимый объект после
			чего указатель на хранимый объект в объекте
			obj_ptr_t обнуляется. Т.е. после вызова метода
			release объект obj_ptr_t больше ничего не
			контролирует.

			\par
			Может возвращать 0, если контролируемого объекта не было.

			\note Введен для похожести на std::auto_ptr
		*/
		T *
		release() {
			T * p = m_p;
			m_p = 0;
			return p;
		}

		/*! Если указатель на хранимый объект не равен \p o,
			то хранимый объект уничтожается. После этого объект
			obj_ptr_t начинает контролировать объект \p o.

			\note Введен для похожести на std::auto_ptr
		*/
		void
		reset(
			//! Указатель на новый контролируемый объект.
			T * o = 0 ) {
			if( o != m_p ) {
				destroy();
			}
			m_p = o;
		}

	private :
		/*! \attention Не имеет реализации, чтобы запретить
			операции копирования.
		*/
		obj_ptr_t( const obj_ptr_t< T > & o );
		/*! \attention Не имеет реализации, чтобы запретить
			операции копирования.
		*/
		obj_ptr_t< T > &
		operator=(
			const obj_ptr_t< T > & o );
};

//
// obj_destruction_t
//

//! Базовый класс, описывающий возможность уничтожения объекта.
/*!
	\since v.3.1
	
	См. auto_ptr_3::soft_obj_ptr_t.
*/
template< class T >
class	obj_destruction_t
{
	private :
		//! Объект который может быть уничтожен.
		T *	m_obj;
		//! Может ли объект быть уничтожен.
		bool	m_is_destroyable;

	public :
		//! Инициализирующий конструктор.
		obj_destruction_t(
			//! Объект который может быть уничтожен.
			T * obj,
			//! Может ли объект быть уничтожен.
			bool is_destroyable )
		:
			m_obj( obj ),
			m_is_destroyable( is_destroyable )
		{
		}

		//! Объект который может быть уничтожен.
		T *
		obj() const
		{
			return m_obj;
		}

		//! Может ли объект быть уничтожен.
		bool
		is_destroyable() const
		{
			return m_is_destroyable;
		}
};

//
// destroyable_obj_t
//

//! Описание объекта, который должен быть уничтожен.
/*!
	\since v.3.1
	
	См. auto_ptr_3::soft_obj_ptr_t.
*/
template< class T >
class	destroyable_obj_t :
	public obj_destruction_t< T >
{
	public :
		//! Инициализирующий конструктор.
		destroyable_obj_t(
			//! Объект, который должен быть уничтожен.
			T * obj )
		:
			obj_destruction_t< T >( obj, true )
		{
		}
};

//
// nondestroyable_obj_t
//

//! Описание объекта, который не должен уничтожаться
/*!
	\since v.3.1
	
	См. auto_ptr_3::soft_obj_ptr_t.
*/
template< class T >
class	nondestroyable_obj_t :
	public obj_destruction_t< T >
{
	public :
		//! Инициализирующий конструктор.
		nondestroyable_obj_t(
			//! Объект, который не должен уничтожаться.
			T & obj )
		:
			obj_destruction_t< T >( &obj, false )
		{
		}
};

//
// soft_obj_ptr_t
//

//! Необязательное удаление одиночного объекта.
/*!
	\since v.3.1

	В некоторых случаях приходится оперировать указателями
	на объекты которые либо нужно удалять, либо нет. Например,
	при организации цепочек ввода-вывода:

\code
// Базовый класс.
class	stream_t
{
	public :
		virtual stream_t &
		out( const char * what ) = 0;
};

// Данный класс используется для помещения данных
// в конкретный файл.
class	file_stream_t : public stream_t
{
	public :
		virtual stream_t &
		out( const char * what );
	...
};

// Класс, который сжимает данные, но для помещения
// их в конкретный приемник использует дополнительный
// объект stream_t.
class	gzip_stream_t : public stream_t
{
	private :
		stream_t *	m_to;
		bool	m_is_must_be_destroyed;

	public :
		gzip_stream_t(
			// Поток, в который нужно реально помещать
			// преобразованные значения.
			stream_t * to,
			// Должен ли реальный поток уничтожаться
			// вместе с нашим объектом?
			bool is_must_be_destroyed )
		:
			m_to( to ),
			m_is_must_be_destroyed( is_must_be_destroyed )
		{
		}
		~gzip_stream_t()
		{
			if( m_is_must_be_destroyed )
				delete m_to;
		}

		virtual stream_t &
		out( const char * what );
	...
};
\endcode

	Использовать в этом случае класс auto_ptr_3::obj_ptr_t
	не очень удобно, т.к. он в деструкторе всегда уничтожает
	контролируемый объект. Для таких случаев и предназначен
	класс soft_obj_ptr_t. С его помощью приведенный пример
	можно переписать так:

\code
// Класс, который сжимает данные, но для помещения
// их в конкретный приемник использует дополнительный
// объект stream_t.
class	gzip_stream_t : public stream_t
{
	private :
		auto_ptr_3::soft_obj_ptr_t< stream_t >	m_to;

	public :
		gzip_stream_t(
			// Поток, в который нужно реально помещать
			// преобразованные значения.
			stream_t * to,
			// Должен ли реальный поток уничтожаться
			// вместе с нашим объектом?
			bool is_must_be_destroyed )
		:
			m_to( to, is_must_be_destroyed )
		{
		}
		~gzip_stream_t()
		{
		}

		virtual stream_t &
		out( const char * what );
	...
};
\endcode

	Если использовать классы destroyable_obj_t и
	nondestroyable_obj_t применение класса %soft_obj_ptr_t
	становится еще более элегантным:

\code
// Класс, который сжимает данные, но для помещения
// их в конкретный приемник использует дополнительный
// объект stream_t.
class	gzip_stream_t : public stream_t
{
	private :
		auto_ptr_3::soft_obj_ptr_t< stream_t >	m_to;

	public :
		typedef auto_ptr_3::destroyable_obj_t< stream_t > destroyable_t;
		typedef auto_ptr_3::nondestroyable_obj_t< stream_t > nondestroyable_t;

		gzip_stream_t(
			// Поток, в который нужно реально помещать
			// преобразованные значения.
			const auto_ptr_3::obj_destruction_t< stream_t > & to )
		:
			m_to( to )
		{
		}
		~gzip_stream_t()
		{
		}

		virtual stream_t &
		out( const char * what );
	...
};

void
use_gzip( stream_t * to )
{
	gzip_stream_t gzip_stream( gzip_stream_t::nondestroyable_t( to ) );
	gzip_stream.out( "Something" );
}
\endcode
*/
template< class T >
class	soft_obj_ptr_t : public obj_ptr_t< T >
{
	//! Псевдоним для базового типа.
	typedef obj_ptr_t< T >	base_type_t;
	public :
		//! Конструктор по-умолчанию.
		soft_obj_ptr_t()
		{
		}
		
		//! Инициализирующий конструктор.
		soft_obj_ptr_t(
			//! Контролируемый объект.
			T * ptr,
			//! Должен ли объект уничтожаться в деструкторе.
			bool is_must_be_destroyed )
		:
			base_type_t( ptr ),
			m_is_must_be_destroyed( is_must_be_destroyed )
		{
		}

		//! Инициализирующий конструктор.
		soft_obj_ptr_t(
			//! Описание объекта который может быть уничтожен.
			const obj_destruction_t< T > & info )
		:
			base_type_t( info.obj() ),
			m_is_must_be_destroyed( info.is_destroyable() )
		{
		}

		~soft_obj_ptr_t()
		{
			if( !m_is_must_be_destroyed )
				// Контролируемый объект не должен
				// уничтожаться.
				base_type_t::clear();
		}

		//! Поменять значение двух объектов soft_obj_ptr_t.
		void
		swap( soft_obj_ptr_t< T > & o )
		{
			if( &o != this )
			{
				bool is_must_be_destroyed = m_is_must_be_destroyed;
				m_is_must_be_destroyed = o.m_is_must_be_destroyed;
				o.m_is_must_be_destroyed = is_must_be_destroyed;

				base_type_t::swap( o );
			}
		}

		//! Заменить контролируемый объект с возможным
		//! уничтожением.
		/*!
			Если есть контролируемый объект и разрешено
			его уничтожение, то контролируемый объект
			уничтожается.
		*/
		void
		reset(
			//! Указатель на новый контролируемый объект.
			T * o = 0,
			//! Разрешается ли удаление нового контролируемого объекта.
			//! На уничтожение текущего контролируемого объекта
			//! никакого влияния не оказывает.
			bool is_must_be_destroyed = true )
		{
			if( !m_is_must_be_destroyed )
				base_type_t::clear();
			base_type_t::reset( o );
			m_is_must_be_destroyed = is_must_be_destroyed;
		}

		//! Заменить контролируемый объект с возможным
		//! уничтожением.
		/*!
			Если есть контролируемый объект и разрешено
			его уничтожение, то контролируемый объект
			уничтожается.
		*/
		void
		reset(
			const obj_destruction_t< T > & info )
		{
			if( !m_is_must_be_destroyed )
				base_type_t::clear();
			base_type_t::reset( info.obj() );
			m_is_must_be_destroyed = info.is_destroyable();
		}

		//! Будет ли уничтожен объект в деструкторе.
		bool
		is_destroyable() const
		{
			return m_is_must_be_destroyed;
		}

		//! Установить возможность уничтожения объекта
		//! в деструкторе.
		/*!
			\return старое значение флага возможности
			уничтожения объекта в деструкторе.
		*/
		bool
		set_destroyable( bool is_destroyable )
		{
			bool r = m_is_must_be_destroyed;
			m_is_must_be_destroyed = is_destroyable;
			
			return r;
		}

	private :
		//! Должен ли уничтожаться контролирующий объект в деструкторе.
		bool	m_is_must_be_destroyed;
};

} /* namespace auto_ptr_3 */

#endif
