|| || Объекты-элементы какого-то прикладного протокола. || {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}} }
|| || Элементы изображения. || {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.
Дело в том, что класс 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 (либо в левом операнде операции копирования) происходит уменьшение количества ссылок. Когда счетчик ссылок обнуляется -- уничтожается контролируемый объект.
При этом вряд ли были бы полезны операторы разыменования указателей (вроде 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 ) };
В схемах сериализации не обязательно описывать в DDL файлы все типы, упомянутые в DDL-схеме. Происходит это потому, что для генерации вспомогательного кода достаточно знать только имя использованного типа. Такой вспомогательный код будет скомпилирован и слинкован, если при компиляции и линковке будут доступны C++ описания всех использованных типов.
Применительно к oess_1::stdsn::shptr_t это означает, что в DDL файле не нужно делать описание shptr_t. Нужно в C++ коде директивой include
подключить заголовочный файл oess_1/stdsn/h/shptr.hpp, а линкеру указать библиотеку oess_stdsn.x.x.x.