【问题标题】:Extending boost serialization扩展boost序列化
【发布时间】:2012-12-27 20:06:58
【问题描述】:

我正在尝试为移动设备的网络视频游戏设置多用途序列化。因为它是联网的,所以在初始连接期间,我需要序列化游戏状态的所有数据,但是,一旦游戏进行中,我只需要序列化某些更改。作为 boost 序列化库一部分的 save 和 load 方法只有一个版本号作为参数。我想做的是有更多的参数,这样我就可以改变保存和加载的条件,而不仅仅是一个版本号。

Boost serialization docs are here, for reference.

下面是简单的 boost 序列化 save 方法目前的样子:

template<class Archive>
void save(Archive& ar, const unsigned int version) const
{
    // Serialize stuff here
}

这是我想要完成的:

template<class Archive>
void save(Archive& ar, const unsigned int version, const unsigned int state_flags) const
{
    if (state_flags & INITIAL_SERIALIZATION)
    {
        // Serialize only data needed for an initial serialization
    }

    // Other serialization
}

我怀疑我是否可以让 boost 库调用我想要的序列化方法,因为它具有重载的运算符来调用具有上述第一个示例中的特定签名的方法。我想象在第一个示例中显示的对save 的调用中调用我自己的save 版本,并且可能从一个单独的位置获取state_flags。有没有人对如何干净地完成这项工作有任何想法,或者有什么好的替代方案?

编辑: 我遇到了另一个问题。我需要序列化不一定是类成员的对象,但文档没有提到对此的任何支持。

这是一个简单的例子:

class Foo
{
private:
    SomeClass m_object;

    template<class Archive>
    void save(Archive& ar, const unsigned int version) const
    {
        Bar* pBar = m_object->getComponent<Bar>();
        ar & pBar;   // <--- But pBar isn't a member of Bar, it's part of SomeClass.
    }
};

我只会序列化 SomeClass 并让它向下渗透到 Bar,但在这种情况下,它是一个属于第三方库/引擎的类,我无法修改。 Boost 序列化是否允许我以这种方式进行序列化和反序列化?

【问题讨论】:

    标签: c++ serialization boost-serialization


    【解决方案1】:

    编辑:在下面添加了新答案以解决实际问题。

    您的问题意味着您反复反序列化到同一个对象。这是否干净是间接的。例如,如果你有一个棋盘,你会想要 同步棋子的初始位置(从上次保存的游戏继续)。为了在游戏进行时传达动作,发送单个动作可能是一个更好的主意 作为单独的对象(然后在收到后应用于板对象),而不是传输整个板对象,如果它已经“初始化”,则只会传输已更改的内容。 这样,您可以先验证输入并忽略无效的移动。无论如何,我只是想提一下,让我们继续。

    如果你有一个对象可能会被同步多次,而成员数据只需要传输一次,那么让对象来决定它是否被“初始化” (因此,如果它需要传输所有内容或只是一个子集)通过使用标志(未序列化)。

    然后你可以检查对象的序列化代码中的标志,就像在你发布的代码中一样(除了标志不是序列化方法的参数, 但是您要反序列化的对象的成员变量)。如果设置了标志,则反序列化所有内容并重置标志。客户端和服务器必须具有相同的标志状态,否则序列化中断。

    或者,您可以先序列化标志,以告诉接收器必须如何执行反序列化(例如,每个成员数据组一个位)。

    请记住,反序列化必须与序列化匹配;您必须按照序列化的顺序提取相同的对象。

    但是,您可以序列化多态类,因为它们是通过类层次结构中的同一级别进行序列化的,因为它们是反序列化的 (如有疑问,在发送时强制转换为基指针并通过基指针反序列化)。

    关于您的第二个问题,您要查找的是non-intrusive serialization。 非侵入式序列化调用独立函数并将要序列化的对象作为参数传递(这就是 std::vector 和 boost::shared_ptr 的序列化方式)。 您可以使用BOOST_SERIALIZATION_SPLIT_FREE 将独立的serialize() 函数拆分为save() 和load()。对于侵入式序列化,它是BOOST_SERIALIZATION_SPLIT_MEMBER。

    要编写通用的反序列化函数(例如通过网络传输对象),您可以使用模板:

    template<typename T>
    void transmit( const T& data ) {
        // ...
        archive << data
        socket << archive_stream;
    }
    

    这种方法的限制是接收者必须知道发送了什么样的对象。如果要发送随机对象,请将它们设为多态:

    IData* data = 0;
    archive >> data;
    switch( data->type() ) {
    case TYPE_INIT:
        return dispatch( static_cast<Board*>(data) );
    case TYPE_MOVE:
        return dispatch( static_cast<Move*>(data) );
    case TYPE_CHAT:
        return dispatch( static_cast<ChatMsg*>(data) );
    }
    

    更新:如果您需要控制(自定义)序列化方法/函数的行为方式,基于被序列化类型未知的状态,您可以实现自己的存档类来保存状态.然后序列化函数可以查询状态并采取相应的行动。

    此状态(或适当的替换)也必须序列化,以指示数据必须如何反序列化。例如,序列化函数的这种“不同行为”可能是某种压缩,而状态是使用的压缩类型。

    这是一个自定义输出存档的最小示例。欲了解更多信息,您可以阅读Derivation from an Existing Archive 并挖掘提升资源。

    给定一个你不能修改的类:

    struct Foo {
        Foo() : i(42), s("foo") {}
        int i;
        std::string s;
    };
    

    您希望基于类未知的条件序列化 i 和/或 s。您可以创建一个包装器来序列化它并添加状态,但如果对象位于向量(或其他类)内,这将不起作用。

    让存档了解状态可能更容易:

    #include <boost/archive/text_oarchive.hpp>
    
    // using struct to omit a bunch of friend declarations    
    struct oarchive : boost::archive::text_oarchive_impl<oarchive>
    {
        oarchive(std::ostream& os, unsigned flags=0)
          : boost::archive::text_oarchive_impl<oarchive>(os,flags),mask(0){}
    
        // forward to base class
        template<class T> void save( T& t ) {
            boost::archive::text_oarchive_impl<oarchive>::save(t);
        }
    
        // this is the 'state' that can be set on the archive
        // and queried by the serialization functions
        unsigned get_mask() const { return mask; }
        void set_mask(unsigned m) { mask = m; }
        void clear_mask() { mask = 0; }
    private:
        unsigned mask;
    };
    
    // explicit instantiation of class templates involved
    namespace boost { namespace archive {
       template class basic_text_oarchive<oarchive>;
       template class text_oarchive_impl<oarchive>;
       template class detail::archive_serializer_map<oarchive>;
    } }
    
    // template implementations (should go to the .cpp)
    #include <boost/archive/impl/basic_text_oarchive.ipp>
    #include <boost/archive/impl/text_oarchive_impl.ipp>
    #include <boost/archive/impl/archive_serializer_map.ipp>
    

    现在要设置和查询的状态:

    enum state { FULL=0x10, PARTIAL=0x20 };
    

    还有一个设置状态的方法(这只是一个非常基本的例子):

    oarchive& operator<<(oarchive& ar, state mask) {
        ar.set_mask(ar.get_mask()|mask);
        return ar;
    }
    

    最后是(非侵入式)序列化函数:

    namespace boost { namespace serialization {
    
    template<class Archive>
    void save(Archive & ar, const Foo& foo, const unsigned int version)
    {
        int mask = ar.get_mask(); // get state from the archive
        ar << mask; // serialize the state! when deserializing,
        // read the state first and extract the data accordingly
    
        if( mask & FULL )
            ar << foo.s; // only serialize s if FULL is set
        ar << foo.i;     // otherwise serialize i only
        ar.clear_mask(); // reset the state
    }
    
    } } // boost::serialization
    
    BOOST_SERIALIZATION_SPLIT_FREE(Foo)
    

    这可以如下使用:

    int main() {
        std::stringstream strm;
        oarchive ar(strm);
    
        Foo f;
        ar << PARTIAL << f << FULL << f;
    
        std::cout << strm.str();
    }
    

    这个例子的目的只是为了说明原理。它对于生产代码来说太基础了。

    【讨论】:

    • Boost 的问题是我必须序列化一个类的成员,在某些情况下我想做的是只序列化一个类的一部分,甚至可能不是成员变量的部分,但是而是由部分成员变量构造的数据。我不会每次都完全序列化所有数据,我只会序列化确定发生了什么变化所需的数据。只有初始序列化会包含所有数据。
    • 基于我的整体需求,我决定不使用 Boost 序列化。大多数情况下,我只是想要一个可以将常见数据类型序列化为二进制的库,但 boost 的方式并不是我想要的。我可能只会编写自己的方法来将类型转换为二进制类型,并使用单独的库为我压缩/解压缩比特流。不过,根据我的问题,您的答案接近我所需要的,因此我将其标记为我的问题的答案。谢谢。
    • @NicFoster 我正要删除答案,因为我误解了你的问题。在我(或其他人)提出适当的解决方案之前,请随意不接受和不赞成。您想要序列化一个您无法在(例如)向量内部修改的类,并且您想要控制如何在不恢复为全局变量的情况下对其进行序列化。
    • 是的。 Here's another thread 当我意识到 Boost 可能不适合我时,我就开始了,它可能有更多关于我需要的细节。我不想将所有这些都包含在这个线程中,因为它与 Boost 序列化无关,而这正是问题所在。我确实在这个线程上想出了一个解决我原来的问题的方法,但它并没有解决我的全部问题,我想在这里发布它会很有用,所以我很快就会这样做。
    • @NicFoster 我已经更新了我的答案以解决实际问题。
    【解决方案2】:

    我确实想出了一个解决方案,虽然不太理想,但我认为它可能值得发布。基本上,我设置了一个单例类来管理发送所有序列化请求,并且该类将跟踪用于该请求的最新位标志。因此,在序列化或反序列化期间,这些方法可以查询这些标志。这让我可以让 Boost 的 save 和 load 方法调用一组更强大的方法,这些方法可以使用这些标志选择性地仅序列化某些成员。

    // Boost's `save` method, which must have this exact signature
    template<class Archive>
    void save(Archive& ar, const unsigned int version) const
    {
        const unsigned int flags = SerializationManager::getFlags(); // SerializationManager is a singleton.
        saveData(ar, version, flags);
    }
    
    // Your own custom method, which can have whichever parameters you need
    template<class Archive>
    void saveData(Archive& ar, const unsigned int version, const unsigned int state_flags) const
    {
        if (state_flags & INITIAL_SERIALIZATION)
        {
            // Serialize only data needed for an initial serialization
        }
    
        // Other serialization
    }
    

    【讨论】:

    • 我目前的想法是创建一个可以保存标志的自定义存档。然后,您可以使用 iomanip 之类的操纵器设置这些标志。例如 archive &lt;&lt; arflag(COMPLETE) &lt;&lt; foo &lt;&lt; arflag(MINIMAL) &lt;&lt; bar; 在您的保存中,您可以简单地使用 ar.get_flags() 查询标志。
    • 我相信像你提到的那样,对于自定义类型,Foo 和Bar 可以实现基于标志的自定义序列化,类似于我上面的内容。我在 boost 中遇到的限制是它需要序列化完整的数据成员,有时我只需要序列化原始数据。例如,如果我有一个std::vector&lt;T&gt; 作为成员变量,但我只需要在网络上序列化它的大小,有没有办法做到这一点而没有另一个代表它的大小的成员变量(这将是非常难以维护)?
    • 更糟糕的情况下,您总是可以为向量本身编写一个非侵入式序列化程序(在这种情况下不要包括/serialization/vector.hpp!)并且只在适当的时候序列化它的大小。但是,您必须告诉接收器它只需要使用标志提取大小(因为您没有发送实际元素)。
    【解决方案3】:

    这里有一个更简单的方法:

    // Boost's `save` method, which must have this exact signature
    template<class Archive>
    void save(Archive& ar, const unsigned int version) const
    {
        const unsigned int flags = SerializationManager::getFlags(); //         SerializationManager is a singleton.
        ar << flags;
        if(flags && INITIAL_SERIALIZATION){
            // Serialize only data needed for an initial serialization
        }
        // Other serialization
    }
    template<class Archive>
    void load(Archive& ar, const unsigned int version) const
    {
        const unsigned int flags = SerializationManager::getFlags(); //         SerializationManager is a singleton.
        unsigned int flags;
        ar >> flags;
        if(flags && INITIAL_SERIALIZATION){
            // Serialize only data needed for an initial serialization
        }
        // Other serialization
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-16
      相关资源
      最近更新 更多