序列化多态类型的常见混淆

Common confusions with serializing polymorphic types

本文关键字:常见混 类型 多态 序列化      更新时间:2023-10-16

我看过许多涉及序列化派生类的问题、教程和文档,但我无法就几个问题达成共识,包括(并在以下代码中说明):

  • boost::serialization::base_objectBOOST_SERIALIZATION_BASE_OBJECT_NVP
  • archive & mData;archive & BOOST_SERIALIZATION_NVP(mData);
  • BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);的用处
  • 要求对层次结构中不需要序列化任何内容的类进行serialize()

法典:

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/base_object.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
    // Even though the class is abstract, we still need this
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        // do nothing
    }
};
// This doesn't seem to do anything
//BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        // These two seem equivalent. Without one of them, unregistered void cast
        archive & boost::serialization::base_object<AbstractPoint>(*this);
        //archive & BOOST_SERIALIZATION_BASE_OBJECT_NVP(AbstractPoint);
        // These two seem equivalent
        archive & mData;
        //archive & BOOST_SERIALIZATION_NVP(mData);
    }
    double mData;
};
int main()
{
    std::shared_ptr<AbstractPoint> point(new Point(7.4));
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive.register_type<Point>();
    outputArchive << point;
    outputStream.close();
    std::shared_ptr<AbstractPoint> pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive.register_type<Point>();
    inputArchive >> pointRead;
    std::shared_ptr<Point> castedPoint = std::dynamic_pointer_cast<Point>(pointRead);
    std::cout << castedPoint->mData << std::endl;
    return 0;
}

另一个主要问题是在"真实"环境中的什么地方注册类(当有链接等时),但这似乎值得单独提问。

文档中有一个"黄金标准"的例子会很棒,但至少在StackOverflow:)

  • boost::serialization::base_objectBOOST_SERIALIZATION_BASE_OBJECT_NVP

NVP 包装器仅适用于具有元素命名的存档,例如 XML。

除非你使用它,否则base_object<>更干净、更简单。

  • archive & mData;archive & BOOST_SERIALIZATION_NVP(mData);

同上

  • BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);的用处

我认为这只是一种优化 - 抑制每个存档类型的注册类型信息,因为您告诉框架它永远不会反序列化该类型的实例

  • 要求对层次结构中不需要序列化任何内容的类进行serialize()

你不需要它,除非你需要关于那里的多态碱基的类型信息。你什么时候需要它?当您需要反序列化基类型的指针时。

因此,如果您有

struct A{ virtual ~A(); };
struct B:A{};
struct C:B{};
struct D:B{};` 

如果您(反)序列化A*,您将需要序列化A(但不是B)。如果您(反)序列化B*,您将需要序列化B

同样,如果你

的类型不是多态的(虚拟的),或者你不使用它,你不需要任何基本序列化(例如,如果你(反)序列化C或直接D)。

最后,如果您有struct A{}; struct B:A{};则根本不需要告诉 Boost 序列化有关基本类型的信息(您可以从 B 中执行序列化)。

更新以响应您的示例:

  1. 案例1.cpp看起来不错
  2. 当然.cpp case2 需要调用基序列化;不一定使用 base_object,因为您需要多态序列化:

    template<class TArchive> void serialize(TArchive& archive, unsigned) {
        archive & boost::serialization::base_object<AbstractPoint>(*this)
                & mData;
        // OR:
        archive & static_cast<AbstractPoint&>(*this) 
                & mData;
        // OR even just:
        archive & mParentData 
                & mData;
    }
    
  3. 案例3.cpp:确实,它与案例1完全相同,但具有动态分配和对象跟踪功能

  4. case4.cpp:与 case1 完全相同,但具有动态分配和对象跟踪功能;铌!!它需要显式序列化基础!

    template<class TArchive> void serialize(TArchive& archive, unsigned) {
        archive & boost::serialization::base_object<AbstractPoint>(*this)
                & mData;
    }
    
  5. 案例 5.cpp:是的,但更典型的做法是使用 boost/serialization/export.hpp 中的CLASS_EXPORT*

比特罗特保险:

  • 案例1.cpp
  • 案例2.cpp
  • 案例3.cpp
  • 案例4.cpp
  • 案例5.cpp

根据@sehe的建议,以下是一些示例用法:

序列化派生类对象,而不是转发到父类对象

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
};
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        archive & mData;
    }
    double mData;
};
int main()
{
    Point point(7.4);
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive << point;
    outputStream.close();
    Point pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive >> pointRead;
    std::cout << pointRead.mData << std::endl;
    return 0;
}

序列化派生类对象,包括(自动)转发到父类对象:

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
    double mParentData = 3.1;
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        archive & mParentData;
    }
};
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        // this is not required, the parent serialize() seems to be called automatically
        // archive & boost::serialization::base_object<AbstractPoint>(*this);
        archive & mData;
    }
    double mData;
};
int main()
{
    Point point(7.4);
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive << point;
    outputStream.close();
    Point pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive >> pointRead;
    std::cout << pointRead.mParentData << std::endl;
    std::cout << pointRead.mData << std::endl;
    return 0;
}

序列化派生类指针,而不是转发到父级(请注意,对象大小写没有任何变化)

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
};
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        archive & mData;
    }
    double mData;
};
int main()
{
    std::shared_ptr<Point> point(new Point(7.4));
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive << point;
    outputStream.close();
    std::shared_ptr<Point> pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive >> pointRead;
    std::cout << pointRead->mData << std::endl;
    return 0;
}

序列化派生类指针,转发到父级(请注意,对象大小写没有任何变化)

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        archive & mParentData;
    }
    double mParentData = 3.1;
};
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        archive & mData;
    }
    double mData;
};
int main()
{
    std::shared_ptr<Point> point(new Point(7.4));
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive << point;
    outputStream.close();
    std::shared_ptr<Point> pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive >> pointRead;
    std::cout << pointRead->mParentData << std::endl;
    std::cout << pointRead->mData << std::endl;
    return 0;
}

序列化基类指针(我们现在必须在存档中注册派生类的类型,并使用boost::serialization::base_object

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/base_object.hpp>
#include <fstream>
class AbstractPoint
{
public:
    virtual ~AbstractPoint(){}
    virtual void DoSomething() = 0;
    // This is required if we want to serialize an AbstractPoint pointer
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        // do nothing
    }
};
class Point : public AbstractPoint
{
public:
    Point() = default;
    Point(const double data) : mData(data) {}
    void DoSomething(){}
    template<class TArchive>
    void serialize(TArchive& archive, const unsigned int version)
    {
        // Without this, we get unregistered void cast
        archive & boost::serialization::base_object<AbstractPoint>(*this);
        archive & mData;
    }
    double mData;
};
int main()
{
    std::shared_ptr<AbstractPoint> point(new Point(7.4));
    std::ofstream outputStream("test.txt");
    boost::archive::text_oarchive outputArchive(outputStream);
    outputArchive.register_type<Point>();
    outputArchive << point;
    outputStream.close();
    std::shared_ptr<AbstractPoint> pointRead;
    std::ifstream inputStream("test.txt");
    boost::archive::text_iarchive inputArchive(inputStream);
    inputArchive.register_type<Point>();
    inputArchive >> pointRead;
    std::shared_ptr<Point> castedPoint = std::dynamic_pointer_cast<Point>(pointRead);
    std::cout << castedPoint->mData << std::endl;
    return 0;
}