序列化多态类型的常见混淆
Common confusions with serializing polymorphic types
我看过许多涉及序列化派生 classes 的问题、教程和文档,但我未能就几个问题达成共识,包括(并在以下代码中说明):
boost::serialization::base_object
对比 BOOST_SERIALIZATION_BASE_OBJECT_NVP
archive & mData;
对比 archive & BOOST_SERIALIZATION_NVP(mData);
BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
的用处
- 需要
serialize()
层次结构中的 class 不需要序列化任何内容。
代码:
#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;
}
另一个主要问题是在 "real" 环境中在哪里注册 classes(当有链接等时),但这似乎值得一个单独的问题。
最好在文档中有一个 "gold standard" 这类事情的例子,但至少在 Whosebug 上:)
boost::serialization::base_object
对比 BOOST_SERIALIZATION_BASE_OBJECT_NVP
只有具有元素命名的档案才需要 NVP 包装器,例如 XML。
除非您使用它,否则 base_object<>
更干净、更简单。
archive & mData;
对比 archive & BOOST_SERIALIZATION_NVP(mData);
同上
BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
的用处
我认为这只是一种优化 - 抑制每个存档类型的注册类型信息,因为你告诉框架它永远不会反序列化该类型的实例
- 需要
serialize()
层次结构中的 class 不需要序列化任何内容。
你不需要它,除非你需要那里关于多态基的类型信息。你什么时候需要那个?当你需要反序列化基类型的指针时。
因此,如果您
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 Serialization 关于基本类型,(你可以在 B
中进行序列化)。
根据您的示例进行更新:
- case1.cpp 看起来不错
case2.cpp 当然需要调用基础序列化;不一定使用 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;
}
case3.cpp:的确,它和案例1完全一样,但是有动态分配和对象跟踪
case4.cpp:和case1一模一样,但是有动态分配和对象跟踪; 注意!! 它需要显式序列化基础!
template<class TArchive> void serialize(TArchive& archive, unsigned) {
archive & boost::serialization::base_object<AbstractPoint>(*this)
& mData;
}
case5.cpp:是的,但更典型的是使用 boost/serialization/export.hpp
中的 CLASS_EXPORT*
宏
Bitrot insurance:
按照@sehe 的建议,这里有一些使用示例:
序列化派生的 class 对象,不转发给父对象
#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;
}
序列化派生的 class 对象,包括(自动)转发给父对象:
#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;
}
序列化派生的class指针,不转发给父
(注意对象案例没有任何变化)
#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;
}
序列化派生的class指针,转发给父
(注意对象案例没有任何变化)
#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;
}
序列化基class指针
(我们现在必须在档案中注册派生的类型 class,以及使用 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;
}
我看过许多涉及序列化派生 classes 的问题、教程和文档,但我未能就几个问题达成共识,包括(并在以下代码中说明):
boost::serialization::base_object
对比BOOST_SERIALIZATION_BASE_OBJECT_NVP
archive & mData;
对比archive & BOOST_SERIALIZATION_NVP(mData);
BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
的用处
- 需要
serialize()
层次结构中的 class 不需要序列化任何内容。
代码:
#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;
}
另一个主要问题是在 "real" 环境中在哪里注册 classes(当有链接等时),但这似乎值得一个单独的问题。
最好在文档中有一个 "gold standard" 这类事情的例子,但至少在 Whosebug 上:)
boost::serialization::base_object
对比BOOST_SERIALIZATION_BASE_OBJECT_NVP
只有具有元素命名的档案才需要 NVP 包装器,例如 XML。
除非您使用它,否则 base_object<>
更干净、更简单。
archive & mData;
对比archive & BOOST_SERIALIZATION_NVP(mData);
同上
BOOST_SERIALIZATION_ASSUME_ABSTRACT(AbstractPoint);
的用处
我认为这只是一种优化 - 抑制每个存档类型的注册类型信息,因为你告诉框架它永远不会反序列化该类型的实例
- 需要
serialize()
层次结构中的 class 不需要序列化任何内容。
你不需要它,除非你需要那里关于多态基的类型信息。你什么时候需要那个?当你需要反序列化基类型的指针时。
因此,如果您
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 Serialization 关于基本类型,(你可以在 B
中进行序列化)。
根据您的示例进行更新:
- case1.cpp 看起来不错
case2.cpp 当然需要调用基础序列化;不一定使用 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; }
case3.cpp:的确,它和案例1完全一样,但是有动态分配和对象跟踪
case4.cpp:和case1一模一样,但是有动态分配和对象跟踪; 注意!! 它需要显式序列化基础!
template<class TArchive> void serialize(TArchive& archive, unsigned) { archive & boost::serialization::base_object<AbstractPoint>(*this) & mData; }
case5.cpp:是的,但更典型的是使用
boost/serialization/export.hpp
中的
CLASS_EXPORT*
宏
Bitrot insurance:
按照@sehe 的建议,这里有一些使用示例:
序列化派生的 class 对象,不转发给父对象
#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;
}
序列化派生的 class 对象,包括(自动)转发给父对象:
#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;
}
序列化派生的class指针,不转发给父 (注意对象案例没有任何变化)
#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;
}
序列化派生的class指针,转发给父 (注意对象案例没有任何变化)
#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;
}
序列化基class指针
(我们现在必须在档案中注册派生的类型 class,以及使用 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;
}