25 May 2011

Сериализация enum

Как известно, в плюсах с сериализацией все очень плохо, т.к. на уровне языка нет рефлекшена.

Максимум, которого можно добиться, дабы облегчить себе жизнь, это возможность писать код сериализации той или иной структуры только один раз (т.е. фактически перечислив все поля), а не два раза под запись в поток и чтение из него. Трюк этот известный, используемый, в том числе, и в библиотеке сериализации в boost -- метод сериализации получает поток неизвестного типа, задаваемого через шаблон, и пишет в него все свои поля через оператор "<<". Cоответственно есть два типа потока -- in и out, у которых перегружен оператор "<<" под то действие, которое надо выполнить. Гениально и просто, и, в свое время, некий подобный велосипед для передачи данных по сети сгородил и я:


    class PacketRead 
    {
    
    public:

        // ...

        PacketRead& operator<<(std::string &v)
        {
            v = m_pack[m_currIndx++].AsString();
            return *this;
        }

        PacketRead& operator<<(int32 &v)
        {
            v = m_pack[m_currIndx++].AsInt32();
            return *this;
        }
        
    };
    
    class PacketWrite
    {
    
    public:
    
        // ... 

        PacketWrite& operator<<(const std::string &v)
        {
            m_pack.WriteString(v);
            return *this;
        }

        PacketWrite& operator<<(const int32 &v)
        {
            m_pack.WriteInt32(v);
            return *this;
        }

    }      


Используются такие потоки примерно так:


    struct CofidecState
    {
        enum CsMode
        {
            mOff,
            mTdmToHandset,
            mTdmToLoud,
        };

        CsMode Mode;

        // in percent 0 .. 100 
        int HandsetMicLevel;
        int HandsetSpeakerLevel;
        int HandsfreeMicLevel;
        int HandsfreeSpeakerLevel;
                
        CofidecState() 
        {
            Mode = mOff;

            HandsetMicLevel = 0;
            HandsetSpeakerLevel = 0;
            HandsfreeMicLevel = 0;
            HandsfreeSpeakerLevel = 0;            
        }

        template<class Data, class TStream>
        static void Serialize(Data &data, TStream &s)
        {
            s 
                << data.Mode
                << data.HandsetMicLevel
                << data.HandsetSpeakerLevel
                << data.HandsfreeMicLevel
                << data.HandsfreeSpeakerLevel;
        }

                        
    };    


Метод Serialize сделан статическим, потому что для обычного метода класса не было бы возможности сделать корректную константность в случае записи в поток. В этом же случае, одна из подстановок типа Data происходит как "const CofidecState &data".

Одни грабли -- обратите внимание на поле Mode, описываемое через enum тип.
Как поддержать запись такого типа потоками?

У меня получилось так:


    class PacketRead 
    {
    
        // ...
    
        template<class TEnum>
        PacketRead& operator<<(TEnum &v)
        {
            BOOST_STATIC_ASSERT(boost::is_enum<TEnum>::value);
            BOOST_STATIC_ASSERT(sizeof(TEnum) == sizeof(int32));

            v = (TEnum)( m_pack[m_currIndx++].AsInt32() );
            return *this;
        }
        
    }
    
    class PacketWrite
    {
    
        // ...
        
        template<class TEnum>
        PacketWrite& operator<<(const TEnum &v)
        {
            BOOST_STATIC_ASSERT(boost::is_enum<TEnum>::value);
            BOOST_STATIC_ASSERT(sizeof(TEnum) == sizeof(int32));

            m_pack.WriteInt32(v);
            return *this;
        }
        
    }



No comments:

Post a Comment