Необходимость класса oess_1::stdsn::shptr_t

Заметки:
Здесь описана только базовая информация о классе oess_1::stdsn::shptr_t. Более подробную информацию о реализации умных указателей в ObjESSty можно получить здесь: oess_1.4.0. Базовый каркас для умных указателей.

Причина появления

Среди типов атрибутов, которые может сериализовать ObjESSty есть указатель на объект, производный от oess_1::stdsn::serializable_t. Это делает возможным сериализацию объектов, чей тип заранее не известен -- известно, только то, что все они производны от одного корня:

Пример 1.
||
|| Объекты-элементы какого-то прикладного протокола.
||
{type	comm_protocol::request_t
}

{type	comm_protocol::reply_t
	{super	comm_protocol::request_t}

	{attr	m_result	{of	oess_1::uint_t}}
}

{type	comm_protocol::handshake_req_t
	{super	comm_protocol::request_t}

	{attr	m_client_name	{of	std::string}}
	{attr	m_connection_type	{of	oess_1::uint_t}}
}

{type	comm_protocol::hanshake_reply_t
	{super	comm_protocol::reply_t}

	{attr	m_session_id	{of	oess_1::uint_t}}
}

||
|| Тип объекта, который передается между клиентом и
|| сервером. Каждая сторона знает, что из коммуникационного
|| канала нужно считать объект comm_protocol::message_t,
|| а затем определять, что именно содержит сообщение.
||
{type	comm_protocol::message_t
	|| Реально, в этом атрибуте может содержаться
	|| как handshake_req_t, так и handshake_reply_t...
	{attr	m_content	{of {ptr}	comm_protocol::request_t}}
}
Пример 2.
||
|| Элементы изображения.
||
{type {abstract}	image::shape_t
}

{type	image::circle_t
	{super	image::shape_t}
}

{type	image::rectangle_t
	{super	image::shape_t}
}

{type	image::curve_t
	{super	image::shape_t}
}

||
|| Само изображение.
||
{type	image::image_t
	{attr	m_name	{of	std::string}}
	{attr	m_items	{stl-list}	{of {ptr}	image::shape_t}}
}
В обоих примерах применение атрибута-указателя является простейшим и, возможно, оптимальным, способом достижения необходимого результата.

Проблема заключается в том, что при использовании простого атрибута-указателя ObjESSty никак не контролирует время жизни объекта по этому указателю. Применительно к классу comm_protocol::message_t это означает, что программист должен уничтожать объект по указателю m_content не только в деструкторе, но и перед каждой операцией десериализации (т.к. после десериализации m_content примет новое значение, а старое значение будет потеряно). Так же программист должен очень внимательно обходится с оператором копирования для сериализуемого типа, содержащего атрибуты-указатели.

Т.е. получается, что применяя простые атрибуты-указатели сериализуемый тип должен быть осложнен контролем атрибута-указателя:

class message_t :
  public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( message_t )
  public :
    message_t()
      :
        m_content( 0 )
    {
    }
    virtual ~message_t()
    {
      if( m_content )
        delete m_content;
    }

  protected :
    // Перед десериализацией нужно уничтожить
    // старое значение m_content.
    virtual void
    oess_pre_unpack()
    {
      if( m_content )
      {
        // Копия указателя для защиты от
        // исключений.
        request_t * p = m_content;
        m_content = 0;

        delete p;
      }
    }

  private :
    // Конструктор копирования и оператор
    // копирования закрыты, чтобы невозможно
    // было копировать объекты message_t из-за
    // атрибута m_content.
    message_t( const message_t & );
    message_t &
    operator=( const message_t & );

    request_t * m_content;
};

При этом нельзя воспользоваться специально предназначенными для целей контроля указателей классами (например, std::auto_ptr), т.к. ObjESSty умеет работать только с атрибутом-указателем.

Во втором примере, со списком указателей, ситуация осложняется тем, что нужно контролировать множество указателей.

Для преодоления этих проблем и был реализован класс oess_1::stdsn::shptr_t.

Класс oess_1::stdsn::shptr_t

Требования

Главной целью при создании класса oess_1::stdsn::shptr_t являлось предоставление ObjESSty возможности контроля атрибутов-указателей сериализуемых объектов. В принципе, для этого можно было бы использовать класс std::auto_ptr. Но с классами, подобными std::auto_ptr, возникают проблемы при использовании их в качестве элементов контейнеров.

Дело в том, что класс std::auto_ptr является единственным владельцем указателя. Оператор копирования std::auto_ptr просто передает права владения и, при этом, правый операнд присваивания вообще теряет указатель:

std::auto_ptr< Some_class > old_ptr( new Some_class() );
// Сейчас 0 != old_ptr.get();

std::auto_ptr< Some_class > new_ptr;
// Сейчас 0 != old_ptr.get();
// 0 == new_ptr.get();

// Передаем владение указателем.
new_ptr = old_ptr;

// Сейчас 0 == old_ptr.get();
// 0 != new_ptr.get();

Такая семантика оператора присваивания означает, что правый операнд не может быть константной ссылкой (т.к. он модифицируется в результате операции). Это делает невозможным создание контейнеров, элементами которых являются std::auto_ptr:

typedef std::auto_ptr< Some_class > some_auto_ptr_t;
typedef std::list< some_auto_ptr_t > ptr_list_t;

void
f()
{
  ptr_list_t l;
  some_auto_ptr_t p( new Some_class() );

  // НЕ КОМПИЛИРУЕТСЯ!
  // Т.к. нет оператора копирования для
  // const some_auto_ptr_t &
  l.push_back( p );
}

Т.о. для ObjESSty требовался такой класс, который бы определял операции копирования, правыми частями которых являлись бы константные ссылки:

class some_ptr_t
{
  public :
    some_ptr_t( const some_ptr_t & );
    some_ptr_t &
    operator=( const some_ptr_t & );
};

В таком случае вставал вопрос: "Что делать с объектом, на который ссылается умный указатель, при копировании указателей?" Можно было бы попробовать делать копию этого объекта. Но это потребовало бы введения в oess_1::stdsn::serializable_t виртуального метода для клонирования. В свою очередь, клонирование для некоторых сериализуемых объектов могло оказаться черезвычайно дорогой операцией.

Поэтому класс oess_1::stdsn::shptr_t реализует т.н. "разделямый указатель". Т.е. существует один объект, указатель на который хранится в нескольких экземплярах shptr_t. Все экземпляры shptr_t используют один счетчик ссылок на объект. Объект уничтожается когда уничтожается последний ссылающийся на него shptr_t. При копировании в shptr_t-приемник передаются указатели на контролируемый объект и на счетчик ссылок, после чего счетчик ссылок увеличивается на 1. При уничтожении shptr_t (либо в левом операнде операции копирования) происходит уменьшение количества ссылок. Когда счетчик ссылок обнуляется -- уничтожается контролируемый объект.

Особенности

Традиционно, классы умных указателей делают в виде шаблонов (C++ template), например, std::auto_ptr или boost::shared_ptr. Для текущего состояния ObjESSty не представляется возможным реализовать сериализацию шаблонных классов. Поэтому, класс shptr_t должен был быть конкретным классом.

При этом вряд ли были бы полезны операторы разыменования указателей (вроде operator*(), operator->()), т.к. класс shptr_t оперирует только указателем на абстрактный класс serializable_t. Программисту все равно пришлось бы приводить serializable_t* или serializable_t& к какому-то своему типу.

Тем не менее, было необходимо, чтобы в прикладных задачах программист мог оперировать умными сериализуемыми указателями на свои типы, на не на общий базовый тип serializable_t. Т.е. чтобы прикладной программист мог описать что-то вроде:

class request_t : public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( request_t )
  ...
};
...
class message_t : public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( message_t )
  private :
    shptr_t< request_t >  m_content;
};

Проблема в том, что нельзя отобразить в DDL С++ конструкцию shptr_t< request_t > m_content.

Поэтому класс oess_1::stdsn::shptr_t предназначен быть базовым классом для конкретных умных указателей:

class request_t : public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( request_t )
  ...
};
class request_ptr_t : public oess_1::stdsn::shptr_t
{
  OESS_SERIALIZER( request_t )
  // Реализованные вручную операторы разыменования
  // указателей.
  ...
};
class message_t : public oess_1::stdsn::serializable_t
{
  OESS_SERIALIZER( message_t )
  private :
    request_ptr_t m_content;
}

Что позволяет описать на DDL следующую схему:

{type	request_t
	...
}
{type	request_ptr_t
	{super oess_1::stdsn::shptr_t}
}
...
{type	message_t
	{attr	m_content	{of request_t}}
}

Естественно, что описывать операторы разыменования в каждом конкретном классе умного указателя слишком трудоемко. Для облегчения этой работы предназначен макрос OESS_1_SHPTR_IFACE(), что позволяет описать класс request_ptr_t всего несколькими строчками:

class request_ptr_t : public oess_1::stdsn::shptr_t
{
  OESS_SERIALIZER( request_ptr_t )
  OESS_1_SHPTR_IFACE( request_ptr_t, request_t,
    oess_1::stdsn::shptr_t )
};

oess_1::stdsn::shptr_t и схемы данных

Схемы данных описываются в DDL файлах. DDL файл подается на вход программе oess_cpp_serializer для генерации вспомогательного кода сериализации/десериализации. В этом случае DDL файлы описывают схемы сериализации.

В схемах сериализации не обязательно описывать в DDL файлы все типы, упомянутые в DDL-схеме. Происходит это потому, что для генерации вспомогательного кода достаточно знать только имя использованного типа. Такой вспомогательный код будет скомпилирован и слинкован, если при компиляции и линковке будут доступны C++ описания всех использованных типов.

Применительно к oess_1::stdsn::shptr_t это означает, что в DDL файле не нужно делать описание shptr_t. Нужно в C++ коде директивой include подключить заголовочный файл oess_1/stdsn/h/shptr.hpp, а линкеру указать библиотеку oess_stdsn.x.x.x.


Документация по ObjESSty. Последние изменения: Fri Oct 13 18:35:37 2006. Создано системой  doxygen 1.4.7
Hosted by uCoz