シリアル化とC ++ 11



C ++で作業する多くの人がこの素晴らしい言語でそれを望んでいると確信しています。オブジェクトをシリアル化する機能は、C#のように簡単です だから私はそれが欲しかった。 そして、なぜ、新しい標準を使用すれば、これは簡単なはずだと思いました。 まず、どのように見えるかを決める必要があります。

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<void()> SomeFunc = [this]() { cout << SomeString; }; };
      
      





まず、これらすべてのラムダをそれ自体に格納し、シリアライズおよびデシリアライズするメソッドを持つSerializableクラスを作成します。

 class Serializable { protected: typedef function<void(const string&)> Func; struct SerializerPair { Func Serializer; Func Deserializer; }; char 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<string, SerializerPair> 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) \ { \ ::Deserialize(_key, x); \ } \ )
      
      





その後、以前のコードはすでにずっと小さく見えました。

 class TestClass : public Serializable { public: int SomeInt = 666; private: serialize(SomeInt); };
      
      





すべてはうまくいきますが、私はあなたがさらに良くできるように思えます。 シリアル化用のコンテナを指定できれば、これにより+10の利便性が得られます。 行う必要があるのは、どのコンテナをスローするかを伝えることができるSerializableからテンプレートクラスを作成することだけです。 男が男が言った。

 template<class Container> class Serializable { protected: typedef function<void(const string&, Container&)> 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<string, SerializerPair> m_Serializers; };
      
      





ContainerInfがなぜ必要なのか疑問に思うかもしれませんが、マクロを正しく作り直すために必要です。 しかし、初心者向けには、シリアライザーの機能をもう少し拡張します。 タイプごとにこれらの関数を記述しないように、グローバル関数のボイラープレートをシリアライズおよびデシリアライズします。 しかし、ここで小さな問題が現れます。 テンプレート関数は、指定した型に対して実行されるため、Serializableから継承されたオブジェクトを個別に取得するように特殊化することはできませんが、((。このため、小さなテンプレートマジックを適用します。

 template<bool UNUSE> struct SerializerEX {}; template<> struct SerializerEX < false > { template<class T, class Cont, class UNUSE> void Serialize(const string& _key, T& _val, Cont& _cont, UNUSE) { ::Serialize(_key, &_val, _cont); } template<class T, class Cont, class UNUSE> void Deserialize(const string& _key, T& _val, Cont& _cont, UNUSE) { ::Deserialize(_key, &_val, _cont); } }; template<> struct SerializerEX < true > { template<class T, class Cont, class UNUSE> void Serialize(const string& _key, T& _val, Cont& _cont, UNUSE) { ::Serialize(_key, (UNUSE)&_val, _cont); } template<class T, class Cont, class UNUSE> 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<decltype(ContainerInf)>::Type& _cont) \ { \ SerializerEX \ < \ CanCast \ < \ Serializable< ClearType<decltype(ContainerInf)>::Type >, \ ClearType<decltype(x)>::Type \ >::Result \ > EX; \ EX.Serialize(_key, x, _cont, (Serializable< ClearType<decltype(ContainerInf)>::Type >*)0); \ }, \ [this](const string& _key, ClearType<decltype(ContainerInf)>::Type& _cont) \ { \ SerializerEX \ < \ CanCast \ < \ Serializable< ClearType<decltype(ContainerInf)>::Type >, \ ClearType<decltype(x)>::Type \ >::Result \ > EX; \ EX.Deserialize(_key, x, _cont, (Serializable< ClearType<decltype(ContainerInf)>::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<pugi::xml_node> { public: int someInt = 0; float X = 0; string ObjectName = "Test"; Transform Transf; map<string, float> 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 ); } }
      
      





出力では

 <?xml version="1.0"?> <Serialization> <NamedPoints> <PI value="3.1415" /> <one value="1" /> <three value="3" /> <two value="2" /> </NamedPoints> <ObjectName value="Hello my little pony" /> <Transf> <Position x="0" y="0" z="0" /> <Rotation x="0" y="0" z="0" /> <Scale x="6" y="6" z="6" /> </Transf> <X value="666" /> </Serialization> Test passed : 1
      
      





やった! すべてが判明し、正常に機能しました。

詳細については、ソースをダウンロードすることをお勧めします。

ソースはこちら---> www.dropbox.com/s/e089fgi3b1jswzf/Serialization.zip?dl=0

UPD。

より多くの型制御のためのより独創的なソリューションを見つけました。

SerializerEXクラスを使用する代わりに、シリアライザーの宣言を少し行うだけで十分でした。 それらを回す

 void Serialize(const string& _key, T* _val, xml_node & _node)
      
      







 void Serialize(const string& _key, T* _val, xml_node & _node,...)
      
      





したがって、 ...へのポインタを指す代わりに、より多くの制御を実現できます。 例えば

 void Serialize(const string& _key, T* _val, xml_node & _node, Widget*) { ... }
      
      





元のタイプを維持しながら、Widget *から継承されたオブジェクトでのみ機能します。

したがって、マクロはより単純なものに変更されます。

 #define serialize(x) char UNNAMED = Add \ ( \ #x, \ [this](const string& _key, ClearType<decltype(ContainerInf)>::Type& _cont) \ { \ ::Serialize(_key, &x, _cont, (ClearType<decltype(x)>::Type*)0); \ }, \ \ [this](const string& _key, ClearType<decltype(ContainerInf)>::Type& _cont) \ { \ ::Deserialize(_key, &x, _cont, (ClearType<decltype(x)>::Type*)0); \ } \ )
      
      






All Articles