Boost 将派生类反序列化为基类指针
Posted
技术标签:
【中文标题】Boost 将派生类反序列化为基类指针【英文标题】:Boost deserialize a derived class to base class pointer 【发布时间】:2012-10-26 15:29:08 【问题描述】:请帮助我将派生类反序列化为基类指针。我附上完整的源代码示例。
request.hpp(无配对 cpp 文件)
#ifndef REQUEST_HPP
#define REQUEST_HPP
#include <memory>
#include <string>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
namespace demo
namespace common
class request
public:
static const int INVALID_ID = -42;
request()
: id_(INVALID_ID), timestamp_(0), source_ip_("unknown") ;
request(int id, long timestamp, const std::string& source_ip)
: id_(id), timestamp_(timestamp), source_ip_(source_ip) ;
virtual ~request() ;
int id() const return id_;
long timestamp() const return timestamp_;
std::string source_ip() const return source_ip_;
protected:
int id_;
long timestamp_;
std::string source_ip_;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned version)
ar & BOOST_SERIALIZATION_NVP(id_);
ar & BOOST_SERIALIZATION_NVP(timestamp_);
ar & BOOST_SERIALIZATION_NVP(source_ip_);
;
typedef std::shared_ptr<request> request_ptr;
;
#endif
command.hpp(派生类)
#ifndef COMMAND_HPP
#define COMMAND_HPP
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include <demo/common/request.hpp>
namespace demo
namespace common
class command : public request
public:
command(): name_("untitled") ;
explicit command(const std::string& name) : name_(name) ;
virtual ~command() ;
virtual void execute();
std::string name() const return name_;
protected:
std::string name_;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned version)
ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(request);
ar & BOOST_SERIALIZATION_NVP(name_);
;
typedef std::shared_ptr<command> command_ptr;
;
BOOST_CLASS_EXPORT_KEY(demo::common::command)
#endif
command.cpp
#include "command.hpp"
#include <iostream>
BOOST_CLASS_EXPORT_IMPLEMENT(demo::common::command)
namespace demo
namespace common
void command::execute()
std::cout << " I am '" + name_ +"' and I am executing..." << std::endl;
;
serializer.hpp
#ifndef SERIALIZER_HPP
#define SERIALIZER_HPP
#include <sstream>
#include <string>
/* classes to serialize */
#include <demo/common/request.hpp>
#include <demo/common/command.hpp>
namespace demo
namespace common
class serializer
public:
serializer() : ;
template<typename T>
std::string serialize(const T& t)
std::stringstream stream;
boost::archive::xml_oarchive archive(stream);
archive << BOOST_SERIALIZATION_NVP(t);
std::string serialized = stream.str();
return serialized;
template<typename T>
void deserialize(const std::string& serialized, T& t)
std::stringstream stream(serialized);
boost::archive::xml_iarchive archive(stream);
archive >> BOOST_SERIALIZATION_NVP(t);
;
#endif
示例用法
#include <iostream>
#include <demo/common/serializer.hpp>
#include <demo/common/command.hpp>
using namespace std;
using namespace demo::common;
int main()
serializer serializer_;
command r("123"); // <-- (1) my desired way of declaring
//request* r = new command("123"); <-- (2) replacing with this makes all work!
//command* r = new command("123"); <-- (3) replacing with this crashes the app, like (1)
std::string s = serializer_.serialize(r);
std::cout << s << std::endl;
request* rr = nullptr;
serializer_.deserialize(s, rr); //this throws an exception
command* rrr = dynamic_cast<command*>(rr);
rrr->execute();
我以为我做了所有需要做的事情,在任何类导出之前包含档案,所有默认构造函数都初始化成员..
请注意,可序列化的类和序列化程序被编译成一个 lib 文件。然后,该库用于两个可以访问标头并链接该库的子项目。它们使用这些类相互通信,通过网络发送序列化对象。
为什么我不能将派生类反序列化为基类指针? 我正在使用 Boost 1.51 和 VC11。
【问题讨论】:
Boost 序列化非常挑剔。确保您的导出链接到同一个模块(假设您上面的所有代码都在您的可执行文件中,您可以)。此外,我在堆栈/堆上混合对象时运气不佳——相反,我发现 always 序列化和反序列化指针更容易(包装在智能指针中) . 等等。request
、command
和 serializer
类在一个项目中,编译为 .lib
,然后在同一个 VS 解决方案中的第二个项目中使用……这是难闻的气味吗?
serializer_
是在哪里定义的?请发布完整的可编译的main()
。
给你,一个完整的主要:)
这可能是一个旧的 boost::serialization 错误。我得到this 分段错误。
【参考方案1】:
问题:
我发现 Boost::serialization 导致我遇到问题的两个主要问题是挑剔且没有足够的文档记录如下:
-
堆栈上的对象与堆上的对象混合的序列化/反序列化。例如,如果您从堆栈上的对象序列化然后尝试反序列化到指针(例如调用您的 load_construct_data)异常可能发生。与相反的情况相同。
没有正确链接您的导出。例如,如果您创建序列化模板/类并将它们放在 .lib 中,则似乎导出可能未正确链接/暴露。这适用于链接并从共享对象/DLL 中使用。
解决方案:
对于#1,我发现最容易制定一个始终序列化/反序列化指针的规则。甚至堆栈上的对象在序列化时也可以使用临时指针以允许此规则。例如:
// serialize
MyObject myobj;
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
MyObject* myObjPtr = &myObj;
oa << myObjPtr; // this is different than oa << myObj!!
std::string serialized = oss.str();
// deserialize
MyObject* myNewObjPtr;
std::stringstream iss(serialized);
boost::archive::text_iarchive ia(iss);
ia >> myNewObjPtr; // invokes new, don't forget to delete (or use smart ptrs!!!)
对于 #2,只需创建一个包含所有导出的 .cpp 文件。将此 CPP 直接链接到您的模块中。换句话说,您将拥有一个带有一堆 BOOST_CLASS_EXPORT_IMPLEMENT() 的 .cpp:
BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);
// ...
更完整的示例: 下面是一个更完整的示例,展示了一些使用非侵入式模板的序列化技巧。侵入式成员方法将非常相似:
MyObject.h
// Can be broken into MyObject.h, MyObject.cpp, MyObjectSerialization.h for example as well.
// This stuff can live in your .lib
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
// assume this class contains GetSomeMember() returning SomeMemberType
class MyObject /* ... */ ;
BOOST_CLASS_EXPORT_KEY(MyObject);
namespace boost namespace serialization
template<class Archive>
void serialize(Archive& ar, MyObject& myObj, const unsigned int version)
ar & myObj.m_someMember;
template<class Archive>
inline void save_construct_data(Archive& ar, const MyObject* myObj, const unsigned int version)
ar & boost::serialization::make_nvp("SomeMemberType", static_cast<const SomeMemberType&>(myObj->GetSomeMember()));
template<class Archive>
inline void load_construct_data(Archive& ar, MyObject* myObj, const unsigned int version)
SomeMemberType t;
ar & boost::serialization::make_nvp("SomeMemberType", t);
::new(myObj)MyObject(t);
// end boost::serialization ns
MyObjectExports.cpp
// This file must be explicitly linked into your module(s) that use serialization.
// This means your executable or shared module/DLLs
#include <boost/serialization/export.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include "MyObject.h"
BOOST_CLASS_EXPORT_IMPLEMENT(MyObject);
【讨论】:
你的想法帮助我移动了一点。想象一下这种情况:我有一个共享的模板化序列化程序。在一个子项目中,我创建了一个request* r = new command()
,但在第二个中我没有——我将所有内容反序列化为request
指针。问题是,如果我从未在第二个子项目中创建 command
对象,则不会实例化适当的模板..(?)。如果我在第二个子项目的代码中的任何位置添加command* c = new command();
,那么一切都可以。看起来这些课程未注册。那么……可以强制注册吗?
在示例中添加了更多内容。也许这会有所帮助。【参考方案2】:
您可能在演示中遇到 input_stream_error,在使用库时可能遇到 unregistered_class 异常。这是由于 boost 自动注册类的方式造成的。
尽管使用了 BOOST_CLASS_EXPORT* 宏,但当您序列化派生对象并反序列化到其基类时,自动注册过程似乎会变得混乱。
但是,您可以在对存档执行任何 i/o 操作之前显式注册类:
// ...
boost::archive::xml_iarchive archive(stream);
// register the class(es) with the archive
archive.template register_type<command>();
archive >> BOOST_SERIALIZATION_NVP(t);
// ...
序列化时使用相同的注册顺序。这使得导出宏变得多余。
【讨论】:
你能解释一下为什么会这样吗?为什么提升导出宏不起作用? 此时我只能猜测。使用宏时,存档(此处为 xml)中存储的信息根据序列化的类型而有所不同;对于 ptr 到 base,类名之类的信息被存储,而 class_id 则相反。这会在反序列化时导致 stream_error。它可能与推导类型的顺序有关。不幸的是,如果不剖析 boost::serialization 库,我无法更具体。 序列化派生的 obj 并反序列化到它的基本 ptr(使用导出宏)如果您修改它来执行此操作,甚至在他们的单元测试 (test_dll_exported.cpp) 中都不起作用。该文档还指出,导出宏旨在通过基本 ptrs [仅?] 进行序列化。此外,派生到基本序列化不在单元测试中。为了安全起见,我只会反序列化基指针。 这似乎有效 - 将所有内容反序列化到/通过基类指针。谢谢,我希望我不会再遇到任何问题.. @AnonymousCoward “序列化时使用相同的注册顺序。这使得导出宏变得多余。”是什么意思?和什么顺序一样?以上是关于Boost 将派生类反序列化为基类指针的主要内容,如果未能解决你的问题,请参考以下文章
使用 JsonConvert.DeserializeObject 或 JObject.Parse 将类反序列化为 c# 中的字典