пятница, 18 марта 2016 г.

[prog.c++14] Еще один кейс для использования шаблонов и CRTP

Разбираясь с примерами одной C++ной библиотеки обнаружил наличие в разных примерах с парсингом бинарных PDU дублирование в классах для PDU дублирование одних и те же методов encode и decode приблизительно вот такого вида:

void encode( std::size_t index, std::uint16_t v )
{
   auto b = &(rep()[index]);
   b[0] = static_cast< std::uint8_t >(v >> 8);
   b[1] = static_cast< std::uint8_t >(v & 0xffu);
}
void decode( std::size_t index, std::uint16_t & v ) const
{
   auto b = &(rep()[index]);
   v = (static_cast< std::uint16_t >(b[0]) << 8) + b[1];
}

Поскольку не люблю копипасту, то задумался, как можно сделать так, чтобы классы для представления PDU с разного размера payload-ами, имели бы общую реализацию методов encode/decode.

Под катом небольшой пример с результатами эксперимента. Код без комментариев, но, надеюсь, он будет понятен. Если же нет, то отвечу на вопросы в комментариях. Однако, сразу поясню пару вещей, которые могут быть не очевидны.

Сделано два класса encoder_t и decoder_t. Это для того, чтобы можно было делать PDU, которые можно только распаковывать, но не нужно упаковывать. Или, наоборот: PDU, которые нужно только упаковывать, но не распаковывать. Ну и, кроме того, так задачка была сложнее :)

Вот такую конструкцию пришлось сделать для того, чтобы сохранить методы data(), encode() и decode() скрытыми (protected).

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 > >
{};

Без этого пришлось бы кроме наследования от encoder_t/decoder_t еще и прописывать эти классы в качестве френдов. Что-то вроде:

class header16_t
   :  protected data_holder_t< 16 >
   ,  protected encoder< header16_t >
   ,  protected decoder< header16_t >
{
   friend class encoder< header16_t >;
   friend class decoder< header16_t >;

Вместо намного более короткого варианта:

class header16_t
   :  protected data_with_encoder_and_decoder_t< 16 >

Итак, вот полный код эксперимента:

#include <cstddef>
#include <cstdint>
#include <array>
#include <iostream>

templateclass D >
class encoder_t
{
   inline std::uint8_t * rep() { return static_cast< D * >(this)->data(); } 
protected :
   void encode( std::size_t index, std::uint8_t v )
   {
      (rep()[index]) = v;
   }

   void encode( std::size_t index, std::uint16_t v )
   {
      auto b = &(rep()[index]);
      b[0] = static_cast< std::uint8_t >(v >> 8);
      b[1] = static_cast< std::uint8_t >(v & 0xffu);
   }

   void encode( std::size_t index, std::uint32_t v )
   {
      auto b = &(rep()[index]);
      b[0] = static_cast< std::uint8_t >(v >> 24);
      b[1] = static_cast< std::uint8_t >((v & 0xff0000ul) >> 16);
      b[2] = static_cast< std::uint8_t >((v & 0xff00ul) >> 8);
      b[3] = static_cast< std::uint8_t >(v & 0xfful);
   }
};

templateclass D >
class decoder_t
{
   inline const std::uint8_t * rep() const { return static_castconst D * >(this)->data(); } 
protected :
   void decode( std::size_t index, std::uint8_t & v ) const
   {
      v = rep()[index];
   }

   void decode( std::size_t index, std::uint16_t & v ) const
   {
      auto b = &(rep()[index]);
      v = (static_cast< std::uint16_t >(b[0]) << 8) + b[1];
   }

   void decode( std::size_t index, std::uint32_t & v ) const
   {
      auto b = &(rep()[index]);
      v = (static_cast< std::uint32_t >(b[0]) << 24)
            + (static_cast< std::uint32_t >(b[1]) << 16)
            + (static_cast< std::uint32_t >(b[2]) << 8)
            + b[3];
   }

   templatetypename P >
   P decode( std::size_t index ) const
   {
      P v; decode( index, v ); return v;
   }
};

template< std::size_t N >
class data_holder_t
{
   std::array< std::uint8_t, N > m_data;

public :
   const std::uint8_t * data() const { return m_data.data(); }
   std::uint8_t * data() { return m_data.data(); }
};

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 > >
{};

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 ); }
};

class header8_t
   :  protected data_with_encoder_and_decoder_t< 8 >
{
public :
   auto f1() const { return decode< std::uint32_t >( 0 ); }
   auto f2() const { return decode< std::uint32_t >( 4 ); }

   void set_f1( std::uint32_t v ) { encode( 0, v ); }
   void set_f2( std::uint32_t v ) { encode( 4, v ); }
};

int main()
{
   header16_t h16;
   h16.set_f1( 0x2189 );
   h16.set_f2( 0x1024 );

   std::cout << std::hex << h16.f1() << ", " << h16.f2() << std::endl;

   if( !( 0x2189 == h16.f1() && 0x1024 == h16.f2() ) )
      std::abort();

   header8_t h8;
   h8.set_f1( 0x21345678 );
   h8.set_f2( 0x43210123 );

   std::cout << std::hex << h8.f1() << ", " << h8.f2() << std::endl;

   if( !( 0x21345678 == h8.f1() && 0x43210123 == h8.f2() ) )
      std::abort();
}

Комментариев нет: