[recovery mode] Сериализация и С++11
Уверен, что многим кто работает с С++ хотелось, чтобы в этом, дивном языке, была возможность сериализовать объекты так же просто, как скажем в С#. Вот и мне этого захотелось. И я подумал, а почему бы и нет, с помощью нового стандарта это должно быть несложно. Для начала стоит определиться с тем, как это должно выглядеть.
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
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
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
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
template<>
struct SerializerEX < false >
{
template
template
template<>
struct SerializerEX < true >
{
template
template
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
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
);
}
}
На выходе получаем