[recovery mode] Сериализация и С++11

f9198bec62714fb893903402ba268cb3.jpgУверен, что многим кто работает с С++ хотелось, чтобы в этом, дивном языке, была возможность сериализовать объекты так же просто, как скажем в С#. Вот и мне этого захотелось. И я подумал, а почему бы и нет, с помощью нового стандарта это должно быть несложно. Для начала стоит определиться с тем, как это должно выглядеть. class Test: public Serializable { public: int SomeInt = 666; float SomeFloat = 42.2; string SomeString = «Hello My Little Pony»; private: serialize (SomeInt); serialize (SomeFloat); serialize (SomeString); }; Такое мне вполне подходило, и я уже представлял себе решение.У нас же есть C++11, а это в свою очередь означало, что у нас в распоряжении имеются лямбды и инициализация полей в объявлении класса. Соответственно можно писать подобные штуки. struct Test { string SomeString = «Hello My Little Pony»; function SomeFunc = [this]() { cout << SomeString; }; }; Для начала напишем класс Serializable который хранил бы в себе все эти лямбды, и имел методы для сериализации и десериализации. class Serializable { protected: typedef function Func;

struct SerializerPair { Func Serializer; Func Deserializer; };

void Add (string _key, Func _serializer, Func _deserializer) { auto& lv_Pair = m_Serializers[_key]; lv_Pair.Serializer = _serializer; lv_Pair.Deserializer = _deserializer; }

public: virtual void Serialize () { for (auto& lv_Ser: m_Serializers) lv_Ser.second.Serializer (lv_Ser.first); }

virtual void Deserialize () { for (auto& lv_Ser: m_Serializers) lv_Ser.second.Deserializer (lv_Ser.first); }

private: map m_Serializers; }; Тут всё просто добавляем лямбды, и потом вызываем их.Добавление выглядит так. class TestClass: public Serializable { public: int SomeInt = 666;

private: char SomeIntSer = Add ( «SomeInt», [this](const string& _key) { :: Serialize (_key, SomeInt); }, [this](const string& _key) { :: Deserialize (_key, SomeInt); } ); }; Функции Serialize и Deserialize выносят саму логику сериализации за пределы класса, что позволяет нам легко расширять функционал.Но это слишком избыточно, не так ли? На данном этапе к нам на помощь приходят макросы. #define UNNAMED_IMPL (x, y) UNNAMED_##x##_##y #define UNNAMED_DECL (x, y) UNNAMED_IMPL (x, y) #define UNNAMED UNNAMED_DECL (__LINE__ , __COUNTER__)

// Макрос UNNAMED нам нужен для генерации не повторяющихся имён

#define serialize (x) char UNNAMED = Add \ (\ #x, \ [this](const string& _key) \ { \ :: Serialize (_key, x); \ }, \ [this](const string& _key) mutable \ { \ :: Deserialize (_key, x); \ } \ ) После этого наш предыдущий код выглядит уже гораздо меньше, и так как я хотел. class TestClass: public Serializable { public: int SomeInt = 666;

private: serialize (SomeInt); }; Всё бы хорошо, но мне кажется, что можно сделать ещё лучше. Если бы мы могли указывать контейнер для сериализации, то это дало бы нам +10 к удобству. Всё что нам нужно, так это сделать из Serializable шаблонный класс, которому мы могли бы сказать какой контейнер нужно прокидывать. Мужик сказал мужик сделал. template class Serializable { protected: typedef function Func;

struct SerializerPair { Func Serializer; Func Deserializer; };

Container* ContainerInf = 0;

char Add (string _key, Func _serializer, Func _deserializer) { auto& lv_Pair = m_Serializers[_key]; lv_Pair.Serializer = _serializer; lv_Pair.Deserializer = _deserializer;

return 0; }

public: virtual void Serialize (Container& _cont) { for (auto& lv_Ser: m_Serializers) lv_Ser.second.Serializer (lv_Ser.first, _cont); }

virtual void Deserialize (Container& _cont) { for (auto& lv_Ser: m_Serializers) lv_Ser.second.Deserializer (lv_Ser.first, _cont); }

private:

map m_Serializers; }; Возможно вам интересно для чего нужен ContainerInf, а нужен он нам для того чтобы грамотно переделать наш макрос. Но для начала расширим возможности нашего сериализатора ещё чуть чуть. Сделаем наши глобальные функции Serialize и Deserialize шаблонными, чтобы не писать для каждого типа эти функции. Но тут появляется маленькая проблема. Шаблонная функция выполняется для того типа который мы ему дали, а потому не получится специализировать её так, чтобы она принимала отдельно объекты которые унаследованы от Serializable, а хочется ((. Для этого применим немножко шаблонной магии. template struct SerializerEX {};

template<> struct SerializerEX < false > { template void Serialize (const string& _key, T& _val, Cont& _cont, UNUSE) { :: Serialize (_key, &_val, _cont); }

template void Deserialize (const string& _key, T& _val, Cont& _cont, UNUSE) { :: Deserialize (_key, &_val, _cont); } };

template<> struct SerializerEX < true > { template void Serialize (const string& _key, T& _val, Cont& _cont, UNUSE) { :: Serialize (_key, (UNUSE)&_val, _cont); }

template void Deserialize (const string& _key, T& _val, Cont& _cont, UNUSE) { :: Deserialize (_key, (UNUSE)&_val, _cont); } }; Теперь мы можем смело переписать наш макрос. #define serialize (x) char UNNAMED = Add \ (\ #x, \ [this](const string& _key, ClearType:: Type& _cont) \ { \ SerializerEX \ < \ CanCast \ < \ Serializable< ClearType:: Type >, \ ClearType:: Type \ >:: Result \ > EX; \ EX.Serialize (_key, x, _cont, (Serializable< ClearType:: Type >*)0); \ }, \ [this](const string& _key, ClearType:: Type& _cont) mutable \ { \ SerializerEX \ < \ CanCast \ < \ Serializable< ClearType:: Type >, \ ClearType:: Type \ >:: Result \ > EX; \ EX.Deserialize (_key, x, _cont, (Serializable< ClearType:: Type >*)0); \ } \ ) Реализацию классов CanCast и ClearType я не буду описывать, они довольно тривиальные, в случае если «Ну очень надо» можно будет посмотреть их в исходниках прикреплённых к статье.Ну и как же тут не показать пример использования. В роли контейнера я выбрал довольно известный Pugi XMLПишем наши проверочные классы. struct Float3 { float X = 0; float Y = 0; float Z = 0; };

class Transform: public Serializable < pugi::xml_node > { public: Float3 Position; Float3 Rotation; Float3 Scale;

private: serialize (Position); serialize (Rotation); serialize (Scale); };

class TestClass: public Serializable {

public: int someInt = 0; float X = 0; string ObjectName = «Test»; Transform Transf; map NamedPoints;

TestClass () { NamedPoints[«one»] = 1; NamedPoints[«two»] = 2; NamedPoints[«three»] = 3; NamedPoints[«PI»] = 3.1415; }

private: serialize (X); serialize (ObjectName); serialize (Transf); serialize (NamedPoints); }; Теперь проверка. void Test () { { TestClass lv_Test; lv_Test.ObjectName = «Hello my little pony»; lv_Test.X = 666; lv_Test.Transf.Scale.X = 6; lv_Test.Transf.Scale.Y = 6; lv_Test.Transf.Scale.Z = 6;

pugi: xml_document doc; auto lv_Node = doc.append_child («Serialization»); lv_Test.Serialize (lv_Node);

doc.save_file (L«Test.xml»); doc.save (cout); }

{ pugi: xml_document doc; doc.load_file (L«Test.xml»); auto lv_Node = doc.child («Serialization»);

TestClass lv_Test; lv_Test.Deserialize (lv_Node);

cout << "Test passed : " << ( lv_Test.X == 666 && lv_Test.ObjectName == "Hello my little pony" && lv_Test.Transf.Scale.X && lv_Test.Transf.Scale.Y && lv_Test.Transf.Scale.Z ); } } На выходе получаем Test passed: 1 Ура! Всё получилось и работет как надо.Для большей информации советую скачать исходники.Исходники тут ---> www.dropbox.com/s/e089fgi3b1jswzf/Serialization.zip? dl=0

© Habrahabr.ru