Продолжение вчерашней заметки.
Во вчерашнем решении очень не понравилось то, что смещение поля в PDU приходилось дублировать при вызовах encode и decode:
class header16_t : protected data_with_encoder_and_decoder_t< 16 > { public : auto f1() const { return decode< std::uint16_t >( 0 ); } auto f2() const { return decode< std::uint16_t >( 2 ); } auto f3() const { return decode< std::uint16_t >( 4 ); } auto f4() const { return decode< std::uint16_t >( 8 ); } void set_f1( std::uint16_t v ) { encode( 0, v ); } void set_f2( std::uint16_t v ) { encode( 2, v ); } void set_f3( std::uint16_t v ) { encode( 4, v ); } void set_f4( std::uint16_t v ) { encode( 8, v ); } }; |
Легко ошибиться. Собственно, я и ошибся: при вызове decode задал правильные смещения, а вот при вызове encode записал неправильные значения. Причем код с этой ошибкой ушел в заметку в блоге и только спустя какое-то время эта ошибка была замечена и исправлена.
Поэтому возник вопрос: а можно ли как-то однократно увязать смещение поле с описанием поля? Чтобы затем смещение автоматически извлекалось из описания поля когда это нужно.
Можно.
Для этого делаем вот такой шаблон:
template< typename SCALAR, std::size_t INDEX > struct field_type { using type = SCALAR; template< typename DECODER > static type decode( DECODER & d ) { return d.template decode< type >( INDEX ); } template< typename ENCODER > static void encode( ENCODER & e, type value ) { e.encode( INDEX, value ); } }; |
После чего вносятся два небольших изменения в созданную ранее инфраструктуру. Во-первых, класс data_with_encoder_decoder_t использует публичное наследование от encoder_t и decoder_t:
template< std::size_t N > class data_with_encoder_and_decoder_t : public data_holder_t< N > , public encoder_t< data_with_encoder_and_decoder_t< N > > , public decoder_t< data_with_encoder_and_decoder_t< N > > {}; |
Нужно это для того, чтобы методы encode и decode были публичными.
Во-вторых, при описании PDU не нужно наследоваться от data_with_encoder_decoder_t. Достаточно просто объявлять атрибут этого типа:
class header16_t { data_with_encoder_and_decoder_t< 16 > m_image; |
Ну и с такими изменениями реализация PDU приобретает вид:
class header16_t { using f1_type = field_type< std::uint16_t, 0 >; using f2_type = field_type< std::uint16_t, 2 >; using f3_type = field_type< std::uint16_t, 4 >; using f4_type = field_type< std::uint16_t, 8 >; data_with_encoder_and_decoder_t< 16 > m_image; public : auto f1() const { return f1_type::decode( m_image ); } auto f2() const { return f2_type::decode( m_image ); } auto f3() const { return f3_type::decode( m_image ); } auto f4() const { return f4_type::decode( m_image ); } void set_f1( f1_type::type v ) { f1_type::encode( m_image, v ); } void set_f2( f2_type::type v ) { f2_type::encode( m_image, v ); } void set_f3( f3_type::type v ) { f3_type::encode( m_image, v ); } void set_f4( f4_type::type v ) { f4_type::encode( m_image, v ); } }; class header8_t { using f1_type = field_type< std::uint32_t, 0 >; using f2_type = field_type< std::uint32_t, 4 >; data_with_encoder_and_decoder_t< 8 > m_image; public : auto f1() const { return f1_type::decode( m_image ); } auto f2() const { return f2_type::decode( m_image ); } void set_f1( f1_type::type v ) { f1_type::encode( m_image, v ); } void set_f2( f2_type::type v ) { f2_type::encode( m_image, v ); } }; |
Имхо, так контроль за действиями программиста получается более строгий.
Конечно, пространство для ошибок все равно остается. Скажем, можно написать:
auto f3() const { return f4_type::decode( m_image ); } |
и такая ошибка будет выявлена только на unit-тестах. Однако, в реальной жизни, скорее всего, вместо f1_type, f2_type и т.д., будет что-то вроде peer_as_number_type, local_as_number_type, interface_index_type и т.д. Поэтому, хоть вероятность глупых опечаток все-таки сохраняется, но она не такая уж и большая.
Ну вот пока и все идеи. Может со временем еще что-нибудь в голову придет.
В online с данным примером можно поиграться здесь: http://goo.gl/xnn9Tt.
Комментариев нет:
Отправить комментарий