使用mixins

时间:2016-02-17 00:25:30

标签: c++ templates serialization boost-serialization

写/读对象时有没有办法处理mixins?我正在使用Boost序列化,但这是一个相当普遍的问题。假设我通过mixins附加了属性,如下所示:

struct Point {
    double x,y;
};

template<class Base>
class MyMixin1 : public Base {
public:
    double someProperty;
};

template<class Base>
class MyMixin2 : public Base {
public:
    double otherProperty;
};

int main() {
    typedef MyMixin2<MyMixin1<PointTypeA> > Mixed12;
    Mixed12 mixed12;

    serialize(mixed12, "someFile.txt");

    Mixed12 mixed12Read = deserialize("someFile.txt");

    return 0;
}

void serialize(Mixed12 object, string filename)
{
    Archive archive(filename);
    WriteIfAvailable(archive, object, someProperty);
    WriteIfAvailable(archive, object, otherProperty);
}

template <typename TObject>
TObject deserialize(string filename)
{
    // How does this function know which data is present and in which order?
    Archive archive(filename);

    TSomeProperty someProperty;
    archive >> someProperty; // We aren't sure if serialize() wrote 'someProperty' first, second, or at all
    AssignIfAvailable(object, someProperty, someProperty);

    TOtherProperty otherProperty;
    archive >> otherProperty; // We aren't sure if serialize() wrote 'otherProperty' first, second, or at all
    AssignIfAvailable(object, otherProperty, otherProperty);
}

serialize()中,我使用SFINAE来编写存档可用的所有属性,但是在deserialize()中,我如何知道编写属性的顺序以反序列化它们?我想在开头写一个“标题”,表示写入的属性和顺序,但是如果我有一个std::vector<Mixed12>那个方法需要为每个点写入标题,这似乎过多了。我也明确地处理std::vector情况(或者更一般地说,可能是碰巧包含Mixin12或Mixin12集合的MyClass)并为外部对象写一个标题,但是这个对象应该依赖于使用其内容的序列化功能,不知道这个头的存在。

最好的结果是我们可以将一种类型(例如Mixed12)写入文件,然后将其读入“兼容”对象(例如Mixed21)。

3 个答案:

答案 0 :(得分:0)

请在下面找到一个完整的工作示例。文本存档用于便于检查 - 您可以看到向量中的输出中没有存储冗余数据。该解决方案基于boost::serialization docs推荐的派生类序列化的最佳实践方法。第三部分演示了如何将基类指针(例如Point)的向量序列化为派生类(例如Mixed12Mixed21)。请注意,由于使用了类GUID,归档中仍然没有冗余(如果想节省空间,可以缩短它们)。您还可以看到指向同一对象的指针是如何重复存储的事实 - boost仅在以后存储索引。

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/export.hpp>
#include <fstream>
#include <vector>

namespace ser=boost::serialization;
namespace arch=boost::archive;
using std::cout;
using std::endl;

struct Point {
    double x,y;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version) {
        ar & x;
        ar & y;
    }
    virtual ~Point() {};
};

template<class Base>
class MyMixin1 : public Base {
    friend class ser::access;
public:
    double someProperty;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version) {
        ar & ser::base_object<Base>(*this);
        ar & someProperty;
    }
    virtual ~MyMixin1() {}
};

template<class Base>
class MyMixin2 : public Base {
public:
    double otherProperty;

    template<class Archive>
    void serialize(Archive &ar, const unsigned int version) {
        ar & ser::base_object<Base>(*this);
        ar & otherProperty;
    }
    virtual ~MyMixin2() {}
};

typedef MyMixin2<MyMixin1<Point> > Mixed12;
typedef MyMixin1<MyMixin2<Point> > Mixed21;


BOOST_CLASS_EXPORT_GUID(Mixed12, "Mixed12")
BOOST_CLASS_EXPORT_GUID(Mixed21, "Mixed21")

int main() {

    Mixed12 mixed12;
    Mixed21 mixed21;

    mixed12.someProperty = 5;
    mixed12.otherProperty = 10;
    mixed12.x = 2;
    mixed12.y = 3;

    mixed21.someProperty = 6;
    mixed21.otherProperty = 7;
    mixed21.x = 9;
    mixed21.y = 8;

    cout << "Testing simple serialization..." << endl;
    {
        std::ofstream f("someFile.txt");
        arch::text_oarchive ar(f);    
        ar << mixed12;
        ar << mixed21;
    }
    cout << "OK" << endl;

    cout << "Testing simple deserialization..." << endl;
    {
        Mixed12 mixed12_deser;
        Mixed21 mixed21_deser;

        std::ifstream f("someFile.txt");
        arch::text_iarchive ar(f);
        ar >> mixed12_deser;
        ar >> mixed21_deser;

        cout << mixed12_deser.someProperty << " " << mixed12_deser.otherProperty
            << " " << mixed12_deser.x << " " << mixed12_deser.y << endl;

        assert(mixed12.someProperty == mixed12_deser.someProperty &&
            mixed12.otherProperty == mixed12_deser.otherProperty &&
            mixed12.x == mixed12_deser.x &&
            mixed12.y == mixed12_deser.y);

        assert(mixed21.someProperty == mixed21_deser.someProperty &&
            mixed21.otherProperty == mixed21_deser.otherProperty &&
            mixed21.x == mixed21_deser.x &&
            mixed21.y == mixed21_deser.y);
    }
    cout << "OK" << endl;

    cout << "Testing vector serialization..." << endl;
    {
        std::vector<Mixed12> v12;
        std::vector<Mixed21> v21;
        std::ofstream f("vectorFile.txt");
        arch::text_oarchive ar(f);

        for (int i = 0; i < 16; i++) v12.push_back(mixed12);
        for (int i = 0; i < 16; i++) v21.push_back(mixed21);

        ar << v12;
        ar << v21;
    }
    cout << "OK" << endl;

    cout << "Testing vector deserialization..." << endl;
    {
        std::vector<Mixed12> v12;
        std::vector<Mixed21> v21;
        std::ifstream f("vectorFile.txt");
        arch::text_iarchive ar(f);

        ar >> v12;
        ar >> v21;

        for (std::vector<Mixed12>::const_iterator it = v12.begin(); it != v12.end(); it++)
            assert(mixed12.someProperty == it->someProperty &&
            mixed12.otherProperty == it->otherProperty &&
            mixed12.x == it->x &&
            mixed12.y == it->y);

        for (std::vector<Mixed21>::const_iterator it = v21.begin(); it != v21.end(); it++)
            assert(mixed21.someProperty == it->someProperty &&
            mixed21.otherProperty == it->otherProperty &&
            mixed21.x == it->x &&
            mixed21.y == it->y);

    }
    cout << "OK!" << endl;

    cout << "Testing base class serialization..." << endl;
    {
        std::vector<Point*> v;
        for (int i = 0; i < 16; i++)
            if (i % 2 == 0)
                v.push_back(&mixed12);
            else
                v.push_back(&mixed21);

        std::ofstream f("basePtrFile.txt");
        arch::text_oarchive ar(f);

        ar << v;
    }
    cout << "OK" << endl;

    cout << "Testing base class deserialization..." << endl;
    {
        std::vector<Point*> v;
        std::ifstream f("basePtrFile.txt");
        arch::text_iarchive ar(f);
        ar >> v;
        assert(v.size() == 16);
        for (int i = 0; i < 16; i++)
            if (i % 2 == 0) {
                assert(dynamic_cast<Mixed12*>(v[i]) != 0);
                assert(dynamic_cast<Mixed21*>(v[i]) == 0);
            } else {
                assert(dynamic_cast<Mixed21*>(v[i]) != 0);
                assert(dynamic_cast<Mixed12*>(v[i]) == 0);
            }
    }
    cout << "OK" << endl;

    return 0;
}

答案 1 :(得分:0)

mixins不是问题。为每种类型创建serialize,并在serialize转发&amp;确保基地也能够编写数据。这些属性将被写入&amp;以确定的方式阅读,不需要额外的数据。对于您的示例代码:

#include "boost/serialization/serialization.hpp"
#include "boost/archive/text_iarchive.hpp"
#include "boost/archive/text_oarchive.hpp"
#include <fstream>
struct Point {
    double x, y;
    template< typename TArch >
    void serialize(TArch& a_Archive, const unsigned int a_Version )
    {
        a_Archive & x & y;
    }
};

template<class Base>
class MyMixin1 : public Base {
public:
    template< typename TArch >
    void serialize(TArch& a_Archive, const unsigned int a_Version )
    {
        a_Archive & someProperty;
        boost::serialization::serialize(a_Archive, static_cast<Base&>(*this), a_Version);
    }
    double someProperty;
};

template<class Base>
class MyMixin2 : public Base {
public:
    template< typename TArch >
    void serialize(TArch& a_Archive, const unsigned int a_Version)
    {
        a_Archive & otherProperty;
        boost::serialization::serialize(a_Archive, static_cast<Base&>(*this), a_Version);
    }
    double otherProperty;
};

int main() {
    typedef MyMixin2<MyMixin1<Point> > Mixed12;
    Mixed12 mixed12;

    {
        std::ofstream ifs("someFile.txt");
        boost::archive::text_oarchive oArch(ifs);
        oArch << mixed12;
    }
    {
        std::ifstream ifs("someFile.txt");
        boost::archive::text_iarchive iArch(ifs);
        iArch >> mixed12;
    }
    return 0;
}

如果您要将指向基类型的指针序列化,则需要一个ID以确保在保存/加载期间调用最派生类型的serializeboost::serialize手册中有一个名为Pointers to Objects of Derived Classes的部分。然而,Mixin与否并没有真正改变任何东西。

答案 2 :(得分:0)

这是使用自定义序列化程序的另一种可能性,它满足您编写Mixed12和读取Mixed21等的要求,并且仍然谨慎地使用布局规范(仅当新布局被序列化时,它被嵌入到文件中,否则使用索引)。有趣的是,您甚至可以为相同的字段名称使用不同类型的类,只要您反序列化为正确的类它将完美地工作,因为只需要知道字段的顺序。免责声明:这是一个概念证明,还有一些需要解决的问题(例如,如果一个字段没有被消费,布局应该包含字段长度以便可以跳过它等等 - 这只是你需要做的一些工作)。

#include <vector>
#include <string>
#include <iostream>
#include <fstream>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>

using std::vector;
using std::string;
namespace ser=boost::serialization;
using boost::archive::text_iarchive;
using boost::archive::text_oarchive;
using std::ifstream;
using std::ofstream;
using std::cout;
using std::endl;


typedef vector<string> FieldNames;


struct Serializer {

    ofstream f;
    text_oarchive ar;
    vector<vector<string> > layouts;

    Serializer(const char *fname): ar(f), f(fname) {
    }

    template <class Class>
    Serializer& operator<<(Class &obj) {

        vector<string> names;
        obj.fieldNames(names);
        unsigned int i;
        for (i = 0; i < layouts.size(); i++)
            if(layouts[i] == names) break;
        if (i == layouts.size()) {
            layouts.push_back(names);
            unsigned int id(0x80000000 | i);
            ar << id;
            ar << names;
            // cout << "New layout: " << i << endl;
        } else {
            // cout << "Reusing layout: " << i << endl;
            ar << i;
        }
        obj.serialize(ar, 0);


        return *this;
    }
};

struct Deserializer {

    ifstream f;
    text_iarchive ar;
    vector<vector<string> > layouts;

    Deserializer (const char *fname): ar(f), f(fname) {
    }

    template <class Class>
    Deserializer& operator>>(Class &obj) {

        unsigned int i;
        ar >> i;
        vector<string> names;
        if (0x80000000 & i) {
            ar >> names;
            // cout << "Reading names..." << endl;
            layouts.push_back(names);
        } else {
            names = layouts[i];
        }


        for (i = 0; i < names.size(); i++) {
            // cout << "Calling deserialize for name:" << names[i] << endl;
            obj.deserialize(ar, names[i]);
        }


        return *this;
    }
};

struct Point {
    int x, y;

    void fieldNames(FieldNames &names) {
        names.push_back("x");
        names.push_back("y");
    }

    void serialize(text_oarchive &ar, unsigned int ver) {
        ar << x;
        ar << y;
    }

    void deserialize(text_iarchive &ar, string &name) {
        // cout << "Point::deserialize() name:" << name << endl;
        if (name == "x") ar >> x;
        if (name == "y") ar >> y;
    }

};

template<class Base>
struct Mixin1: public Base {
    int a;

    void fieldNames(FieldNames &names) {
        Base::fieldNames(names);
        names.push_back("a");
    }

    void serialize(text_oarchive &ar, unsigned int ver) {
        Base::serialize(ar, ver);
        ar << a;
    }

    void deserialize(text_iarchive &ar, string &name) {
        Base::deserialize(ar, name);
        if (name == "a") ar >> a;
    }
};

template<class Base>
struct Mixin2: public Base {
    int b;

    void fieldNames(FieldNames &names) {
        Base::fieldNames(names);
        names.push_back("b");
    }

    void serialize(text_oarchive &ar, unsigned int ver) {
        Base::serialize(ar, ver);
        ar << b;
    }

    void deserialize(text_iarchive &ar, string &name) {
        Base::deserialize(ar, name);
        if (name == "b") ar >> b;
    }
};

typedef Mixin1<Mixin2<Point> > Mixed12;
typedef Mixin2<Mixin1<Point> > Mixed21;

int main() {
    cout << "Testing serialization" << endl;
    {
        Serializer ser("ser2_test.txt");
        Mixed12 m12;
        Mixed21 m21;
        m12.x = 1; m12.y = 2; m12.a = 3; m12.b = 4;        
        m21.x = 5; m21.y = 6; m21.a = 7; m21.b = 8;
        for (int i = 0; i < 100; i++) {
            ser << m12;
            ser << m21;
        }
    }
    cout << "OK" << endl;

    cout << "Testing deserialization to Mixed12" << endl;
    {
        Deserializer des("ser2_test.txt");
        Mixed12 m12;
        for (int i = 0; i < 100; i++) {
            des >> m12;
            // cout << "m12: " << m12.x << " " << m12.y << " " << m12.a << " " << m12.b << endl;
            assert(m12.x == 1 && m12.y == 2 && m12.a == 3 && m12.b == 4);
            des >> m12;
            assert(m12.x == 5 && m12.y == 6 && m12.a == 7 && m12.b == 8);
        }
    }
    cout << "OK" << endl;

    cout << "Testing deserialization to Mixed21" << endl;
    {
        Deserializer des("ser2_test.txt");
        Mixed21 m21;
        for (int i = 0; i < 100; i++) {
            des >> m21;
            assert(m21.x == 1 && m21.y == 2 && m21.a == 3 && m21.b == 4);
            des >> m21;
            assert(m21.x == 5 && m21.y == 6 && m21.a == 7 && m21.b == 8);
        }
    }
    cout << "OK" << endl;
}