Path: | docs/examples/cpp/custom/submit_deliver |
Last Update: | Mon Jun 04 14:34:34 +0400 2007 |
Однажны потребовалось создать C++ класс, который бы хранил несколько полей (с перспективой увеличения их числа в ближайшем будущем). Проблема была в том, что для каждого поля нужно было иметь четыре метода (например, для поля с именем source_addr_ton):
bool is_source_addr_ton_present() const; oess_1::uchar_t source_addr_ton() const; void set_source_addr_ton( oess_1::uchar_t v ); void drop_source_addr_ton();
Все еще усугублялось тем, что признак наличия актуального значения у поля должен быть выставлен в специальной битовой маске. И если вызывается метод getter, а бит в этой маске не выставлен, то getter должен порождать исключение. Соответственно, метод setter должен выставлять соответствующий бит в битовой маске. Т.е. что-то типа:
bool is_source_addr_ton_present() const { return ( 0 != ( m_fields_bit_mask & 1 ) ); } oess_1::uchar_t source_addr_ton() const { if( !is_source_addr_ton_present() ) throw std::runtime_error( "source_addr_ton: no actual value" ); return m_source_addr_ton; } void set_source_addr_ton( oess_1::uchar_t v ) { m_source_addr_ton = v; m_fields_bit_mask |= 1; } void drop_source_addr_ton() { m_fields_bit_mask &= ~1; }
Можно было бы применить обычный copy-paste. А затем править названия полей и методов, ожидая, что где-то что-то окажется забытым.
Можно было бы написать пару макросов и использовать их:
class submit_deliver_t { public : DECL_FIELD( source_addr_ton, oess_1::uchar_t, 0, 1 ) DECL_FIELD( source_addr_npi, oess_1::uchar_t, 0, 2 ) DECL_FIELD( dest_addr_ton, oess_1::uchar_t, 0, 4 ) ... }; IMPL_FIELD( source_addr_ton, oess_1::uchar_t, 0, 1 ) IMPL_FIELD( source_addr_npi, oess_1::uchar_t, 0, 2 ) IMPL_FIELD( dest_addr_ton, oess_1::uchar_t, 0, 4 )
Однако макросы получаются непрозрачными для инструментов типа ctags и doxygen.
В результате был сделан кодогенератор на Ruby. Первоначально этот кодогенератор работал без использования RuCodeGen (см. обсуждение на RSDN.RU). Здесь же приводится адаптированная к RuCodeGen версия этого генератора.
require 'submit_deliver_gen' submit_deliver do |c| c.decl_file :script_relative => "submit_deliver.detail.hpp" c.impl_file :script_relative => "submit_deliver.detail.cpp" c.field :name => :source_addr_ton, :type => "oess_1::uchar_t", :bit_mask => 0x1, :default => 0 c.field :name => :source_addr_npi, :type => "oess_1::uchar_t", :bit_mask => 0x2, :default => 0 c.field :name => :dest_addr_ton, :type => "oess_1::uchar_t", :bit_mask => 0x4, :default => 0 c.field :name => :dest_addr_npi, :type => "oess_1::uchar_t", :bit_mask => 0x8, :default => 0 c.field :name => :esm_class, :type => "oess_1::uchar_t", :bit_mask => 0x10, :default => 0 c.field :name => :protocol_id, :type => "oess_1::uchar_t", :bit_mask => 0x20, :default => 0 c.field :name => :data_coding, :type => "oess_1::uchar_t", :bit_mask => 0x40, :default => 0 end
require 'rubygems' require_gem 'RuCodeGen' module Submit_Deliver # Класс для хранения описания одного поля. # Так же отвечает за генерацию кода, связанного с одним полем. class Field # Инициализирующий конструктор. # # _a_field_params_ Hash с параметрами очередного поля. # Должен содержать значения для ключей: # [:name] название поля (например, source_addr_ton); # [:type] имя C++ типа для представления поля (например, oess_1::uchar_t); # [:default] значение данного поля по умолчанию; # [:bit_mask] значение битовой маски, которое будет указывать # наличие данного поля. # Если какого-то из обязательных ключей нет, то порождается # исключение ArgumentError. def initialize( a_field_params ) check_mandatory_key( a_field_params, :name ) check_mandatory_key( a_field_params, :type ) check_mandatory_key( a_field_params, :default ) check_mandatory_key( a_field_params, :bit_mask ) @params = a_field_params end # Сгенерировать декларацию необходимых методов для поля в описании класса. def gen_method_decl <<-EOF /*! \\return true, если значение для поля #{name} задано. */ bool is_#{name}_present() const; /*! \\return значение поля #{name}. \\throw std::exception при попытке получить значение отсутствующего поля. */ #{type} #{name}() const; /*! Установить значение поля #{name}. Одновременно выставляется признак того, что поле задано. */ void set_#{name}( #{type} v ); /*! Сбросить признак того, что поле #{name} задано. */ void drop_#{name}(); EOF end # Сгенерировать декларацию самого поля в описании класса. def gen_field_decl <<-EOF /*! Значение поля #{name}. Определено только, если в m_fields_bit_mask установлены биты #{bit_mask}. */ #{type} #{attr_name}; EOF end # Сгенерировать инициализацию значением по умолчанию в конструкторе. def gen_default_init <<-EOF , m_#{name}( #{default} ) EOF end # Сгенерировать реализацию методов по работе с полем в реализации класса. # # _a_class_name_ имя C++ класса, для которого генерируются методы. def gen_method_impl( a_class_name ) <<EOF bool #{a_class_name}::is_#{name}_present() const { return ( #{bit_mask} == ( m_fields_bit_mask & #{bit_mask} ) ); } #{type} #{a_class_name}::#{name}() const { if( !is_#{name}_present() ) throw std::runtime_error( "#{name}: no actual value" ); return #{attr_name}; } void #{a_class_name}::set_#{name}( #{type} v ) { #{attr_name} = v; m_fields_bit_mask |= #{bit_mask}; } void #{a_class_name}::drop_#{name}() { m_fields_bit_mask &= ~#{bit_mask}; } EOF end protected # Проверка наличия указанного ключа в указанном HashMap-е. # Порождает исключение ArgumentError, если ключа в HashMap-е нет. def check_mandatory_key( a_hash, a_key ) raise ArgumentError.new( "key '#{a_key}' missed" ) unless a_hash.has_key?( a_key ) end # Getter для значения имени поля. def name() @params[ :name ] end # Getter для названия атрибута в C++ классе. def attr_name() "m_" + name.to_s end # Getter для значения типа поля. def type() @params[ :type ] end # Getter для значения по умолчанию для поля. def default() @params[ :default ] end # Getter для битовой маски поля. def bit_mask() @params[ :bit_mask ] end end # class Field # Класс для хранения описания всех полей. # Так же отвечает за генерацию кода, связанного со всем классом. class Data def initialize @fields = [] @decl_file = nil @impl_file = nil end # Добавление еще одного поля. # # _a_field_params_ параметры, которые передаются затем в конструктор # класса Field. def field( a_field_params ) f = Field.new( a_field_params ) @fields << f end # Метаметоды, которые упростят создание getter-ов и setter-ов # для атрибутов ValueIncapsulator. def self.getter_setter_for( a_attribute ) class_eval %Q{ def get_#{a_attribute} @#{a_attribute} end def #{a_attribute}( a_value ) @#{a_attribute} = a_value end } end getter_setter_for :decl_file getter_setter_for :impl_file # Генерация описания C++ класса. def gen_class_decl <<EOF /*! Класс для хранения опциональных значений SMPP-полей для submit_sm/deliver_sm/data_sm PDU. Поле считается имеющим актуальное значение, если соответствующий метод is_*_present возвращает true. В противном случае поле считается не заданным и обращение к нему будет приводить к возникновению исключения std::exception. */ class MBSMS_2_TYPE submit_deliver_t : public oess_1::stdsn::serializable_t { OESS_SERIALIZER_EX( submit_deliver_t, MBSMS_2_TYPE ) public : /*! Конструктор по умолчанию. Все поля считаются не имеющими актуального значения. */ submit_deliver_t(); virtual ~submit_deliver_t(); //! Тип тега в TLV поле. typedef oess_1::ushort_t tag_t; //! Тип карты необязательных TLV полей. typedef std::map< tag_t, std::string > tlv_map_t; #{@fields.inject( "" ) do |decls, f| decls += f.gen_method_decl end} //! Получение карты необязательных TLV полей для изменения. tlv_map_t & tlv_fields(); //! Получение карты необязательных TLV полей для чтения. const tlv_map_t & tlv_fields() const; //! Сброс карты необязательных TLV полей. void drop_tlv_fields(); private : /*! Битовая маска, которая определяет, какие именно поля имеют актуальные значения. */ oess_1::uint_t m_fields_bit_mask; #{@fields.inject( "" ) do |decls, f| decls += f.gen_field_decl end} //! Карта необязательных TLV полей. tlv_map_t m_tlv_fields; }; EOF end # Генерация реализации C++ класса. def gen_class_impl <<EOF submit_deliver_t::submit_deliver_t() : m_fields_bit_mask( 0 ) #{@fields.inject( "" ) do |decls, f| decls += f.gen_default_init end} {} submit_deliver_t::~submit_deliver_t() {} #{@fields.inject( "" ) do |decls, f| decls += f.gen_method_impl( 'submit_deliver_t' ) end} submit_deliver_t::tlv_map_t & submit_deliver_t::tlv_fields() { return m_tlv_fields; } const submit_deliver_t::tlv_map_t & submit_deliver_t::tlv_fields() const { return m_tlv_fields; } void submit_deliver_t::drop_tlv_fields() { tlv_map_t empty; empty.swap( m_tlv_fields ); } EOF end # Класс, который будет выполнять генерацию описания типа submit_deliver_t. class DeclGenerator def initialize( d ) @data = d end def generate( to ) to << @data.gen_class_decl end end # Класс, который будет выполнять генерацию определения типа submit_deliver_t. class ImplGenerator def initialize( d ) @data = d end def generate( to ) to << @data.gen_class_impl end end end # class Data end # module Submit_Deliver # Функция, предназначенная для генерации C++ кода по метаданным. # # _block_ блок кода с определением метаданных. def submit_deliver( &block ) d = Submit_Deliver::Data.new block.call( d ) fail "decl_file not specified" if nil == d.get_decl_file fail "impl_file not specified" if nil == d.get_impl_file RuCodeGen::Generators.add( RuCodeGen::FilenameProducer.produce( $0, d.get_decl_file ), Submit_Deliver::Data::DeclGenerator.new( d ) ) RuCodeGen::Generators.add( RuCodeGen::FilenameProducer.produce( $0, d.get_impl_file ), Submit_Deliver::Data::ImplGenerator.new( d ) ) end if $0 == __FILE__ submit_deliver do |c| c.decl_file :script_relative => "test.hpp" c.impl_file :script_relative => "test.cpp" c.field :name => :source_addr_ton, :type => "oess_1::uchar_t", :bit_mask => 0x1, :default => 0 c.field :name => :source_addr_npi, :type => "oess_1::uchar_t", :bit_mask => 0x2, :default => 0 end end
/*! Класс для хранения опциональных значений SMPP-полей для submit_sm/deliver_sm/data_sm PDU. Поле считается имеющим актуальное значение, если соответствующий метод is_*_present возвращает true. В противном случае поле считается не заданным и обращение к нему будет приводить к возникновению исключения std::exception. */ class MBSMS_2_TYPE submit_deliver_t : public oess_1::stdsn::serializable_t { OESS_SERIALIZER_EX( submit_deliver_t, MBSMS_2_TYPE ) public : /*! Конструктор по умолчанию. Все поля считаются не имеющими актуального значения. */ submit_deliver_t(); virtual ~submit_deliver_t(); //! Тип тега в TLV поле. typedef oess_1::ushort_t tag_t; //! Тип карты необязательных TLV полей. typedef std::map< tag_t, std::string > tlv_map_t; /*! \return true, если значение для поля source_addr_ton задано. */ bool is_source_addr_ton_present() const; /*! \return значение поля source_addr_ton. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t source_addr_ton() const; /*! Установить значение поля source_addr_ton. Одновременно выставляется признак того, что поле задано. */ void set_source_addr_ton( oess_1::uchar_t v ); /*! Сбросить признак того, что поле source_addr_ton задано. */ void drop_source_addr_ton(); /*! \return true, если значение для поля source_addr_npi задано. */ bool is_source_addr_npi_present() const; /*! \return значение поля source_addr_npi. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t source_addr_npi() const; /*! Установить значение поля source_addr_npi. Одновременно выставляется признак того, что поле задано. */ void set_source_addr_npi( oess_1::uchar_t v ); /*! Сбросить признак того, что поле source_addr_npi задано. */ void drop_source_addr_npi(); /*! \return true, если значение для поля dest_addr_ton задано. */ bool is_dest_addr_ton_present() const; /*! \return значение поля dest_addr_ton. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t dest_addr_ton() const; /*! Установить значение поля dest_addr_ton. Одновременно выставляется признак того, что поле задано. */ void set_dest_addr_ton( oess_1::uchar_t v ); /*! Сбросить признак того, что поле dest_addr_ton задано. */ void drop_dest_addr_ton(); /*! \return true, если значение для поля dest_addr_npi задано. */ bool is_dest_addr_npi_present() const; /*! \return значение поля dest_addr_npi. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t dest_addr_npi() const; /*! Установить значение поля dest_addr_npi. Одновременно выставляется признак того, что поле задано. */ void set_dest_addr_npi( oess_1::uchar_t v ); /*! Сбросить признак того, что поле dest_addr_npi задано. */ void drop_dest_addr_npi(); /*! \return true, если значение для поля esm_class задано. */ bool is_esm_class_present() const; /*! \return значение поля esm_class. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t esm_class() const; /*! Установить значение поля esm_class. Одновременно выставляется признак того, что поле задано. */ void set_esm_class( oess_1::uchar_t v ); /*! Сбросить признак того, что поле esm_class задано. */ void drop_esm_class(); /*! \return true, если значение для поля protocol_id задано. */ bool is_protocol_id_present() const; /*! \return значение поля protocol_id. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t protocol_id() const; /*! Установить значение поля protocol_id. Одновременно выставляется признак того, что поле задано. */ void set_protocol_id( oess_1::uchar_t v ); /*! Сбросить признак того, что поле protocol_id задано. */ void drop_protocol_id(); /*! \return true, если значение для поля data_coding задано. */ bool is_data_coding_present() const; /*! \return значение поля data_coding. \throw std::exception при попытке получить значение отсутствующего поля. */ oess_1::uchar_t data_coding() const; /*! Установить значение поля data_coding. Одновременно выставляется признак того, что поле задано. */ void set_data_coding( oess_1::uchar_t v ); /*! Сбросить признак того, что поле data_coding задано. */ void drop_data_coding(); //! Получение карты необязательных TLV полей для изменения. tlv_map_t & tlv_fields(); //! Получение карты необязательных TLV полей для чтения. const tlv_map_t & tlv_fields() const; //! Сброс карты необязательных TLV полей. void drop_tlv_fields(); private : /*! Битовая маска, которая определяет, какие именно поля имеют актуальные значения. */ oess_1::uint_t m_fields_bit_mask; /*! Значение поля source_addr_ton. Определено только, если в m_fields_bit_mask установлены биты 1. */ oess_1::uchar_t m_source_addr_ton; /*! Значение поля source_addr_npi. Определено только, если в m_fields_bit_mask установлены биты 2. */ oess_1::uchar_t m_source_addr_npi; /*! Значение поля dest_addr_ton. Определено только, если в m_fields_bit_mask установлены биты 4. */ oess_1::uchar_t m_dest_addr_ton; /*! Значение поля dest_addr_npi. Определено только, если в m_fields_bit_mask установлены биты 8. */ oess_1::uchar_t m_dest_addr_npi; /*! Значение поля esm_class. Определено только, если в m_fields_bit_mask установлены биты 16. */ oess_1::uchar_t m_esm_class; /*! Значение поля protocol_id. Определено только, если в m_fields_bit_mask установлены биты 32. */ oess_1::uchar_t m_protocol_id; /*! Значение поля data_coding. Определено только, если в m_fields_bit_mask установлены биты 64. */ oess_1::uchar_t m_data_coding; //! Карта необязательных TLV полей. tlv_map_t m_tlv_fields; };
submit_deliver_t::submit_deliver_t() : m_fields_bit_mask( 0 ) , m_source_addr_ton( 0 ) , m_source_addr_npi( 0 ) , m_dest_addr_ton( 0 ) , m_dest_addr_npi( 0 ) , m_esm_class( 0 ) , m_protocol_id( 0 ) , m_data_coding( 0 ) {} submit_deliver_t::~submit_deliver_t() {} bool submit_deliver_t::is_source_addr_ton_present() const { return ( 1 == ( m_fields_bit_mask & 1 ) ); } oess_1::uchar_t submit_deliver_t::source_addr_ton() const { if( !is_source_addr_ton_present() ) throw std::runtime_error( "source_addr_ton: no actual value" ); return m_source_addr_ton; } void submit_deliver_t::set_source_addr_ton( oess_1::uchar_t v ) { m_source_addr_ton = v; m_fields_bit_mask |= 1; } void submit_deliver_t::drop_source_addr_ton() { m_fields_bit_mask &= ~1; } bool submit_deliver_t::is_source_addr_npi_present() const { return ( 2 == ( m_fields_bit_mask & 2 ) ); } oess_1::uchar_t submit_deliver_t::source_addr_npi() const { if( !is_source_addr_npi_present() ) throw std::runtime_error( "source_addr_npi: no actual value" ); return m_source_addr_npi; } void submit_deliver_t::set_source_addr_npi( oess_1::uchar_t v ) { m_source_addr_npi = v; m_fields_bit_mask |= 2; } void submit_deliver_t::drop_source_addr_npi() { m_fields_bit_mask &= ~2; } bool submit_deliver_t::is_dest_addr_ton_present() const { return ( 4 == ( m_fields_bit_mask & 4 ) ); } oess_1::uchar_t submit_deliver_t::dest_addr_ton() const { if( !is_dest_addr_ton_present() ) throw std::runtime_error( "dest_addr_ton: no actual value" ); return m_dest_addr_ton; } void submit_deliver_t::set_dest_addr_ton( oess_1::uchar_t v ) { m_dest_addr_ton = v; m_fields_bit_mask |= 4; } void submit_deliver_t::drop_dest_addr_ton() { m_fields_bit_mask &= ~4; } bool submit_deliver_t::is_dest_addr_npi_present() const { return ( 8 == ( m_fields_bit_mask & 8 ) ); } oess_1::uchar_t submit_deliver_t::dest_addr_npi() const { if( !is_dest_addr_npi_present() ) throw std::runtime_error( "dest_addr_npi: no actual value" ); return m_dest_addr_npi; } void submit_deliver_t::set_dest_addr_npi( oess_1::uchar_t v ) { m_dest_addr_npi = v; m_fields_bit_mask |= 8; } void submit_deliver_t::drop_dest_addr_npi() { m_fields_bit_mask &= ~8; } bool submit_deliver_t::is_esm_class_present() const { return ( 16 == ( m_fields_bit_mask & 16 ) ); } oess_1::uchar_t submit_deliver_t::esm_class() const { if( !is_esm_class_present() ) throw std::runtime_error( "esm_class: no actual value" ); return m_esm_class; } void submit_deliver_t::set_esm_class( oess_1::uchar_t v ) { m_esm_class = v; m_fields_bit_mask |= 16; } void submit_deliver_t::drop_esm_class() { m_fields_bit_mask &= ~16; } bool submit_deliver_t::is_protocol_id_present() const { return ( 32 == ( m_fields_bit_mask & 32 ) ); } oess_1::uchar_t submit_deliver_t::protocol_id() const { if( !is_protocol_id_present() ) throw std::runtime_error( "protocol_id: no actual value" ); return m_protocol_id; } void submit_deliver_t::set_protocol_id( oess_1::uchar_t v ) { m_protocol_id = v; m_fields_bit_mask |= 32; } void submit_deliver_t::drop_protocol_id() { m_fields_bit_mask &= ~32; } bool submit_deliver_t::is_data_coding_present() const { return ( 64 == ( m_fields_bit_mask & 64 ) ); } oess_1::uchar_t submit_deliver_t::data_coding() const { if( !is_data_coding_present() ) throw std::runtime_error( "data_coding: no actual value" ); return m_data_coding; } void submit_deliver_t::set_data_coding( oess_1::uchar_t v ) { m_data_coding = v; m_fields_bit_mask |= 64; } void submit_deliver_t::drop_data_coding() { m_fields_bit_mask &= ~64; } submit_deliver_t::tlv_map_t & submit_deliver_t::tlv_fields() { return m_tlv_fields; } const submit_deliver_t::tlv_map_t & submit_deliver_t::tlv_fields() const { return m_tlv_fields; } void submit_deliver_t::drop_tlv_fields() { tlv_map_t empty; empty.swap( m_tlv_fields ); }