C++ 模板工厂构造函数/反序列化

Posted

技术标签:

【中文标题】C++ 模板工厂构造函数/反序列化【英文标题】:C++ templated factory constructor/de-serialization 【发布时间】:2010-05-24 22:21:07 【问题描述】:

我在看boost序列化库,提供序列化支持的侵入式方法是定义一个带有签名的成员函数(简化):

class ToBeSerialized 
public:
    //Define this to support serialization
    //Notice not virtual function!
    template<class Archive>
    void serialize(Archive & ar)
    .....
;

此外,通过基指针支持派生类序列化的一种方法是使用以下类型的宏:

//No mention to the base class(es) from which Derived_class inherits
BOOST_CLASS_EXPORT_GUID(Derived_class, "derived_class")

其中 Derived_class 是从基类继承的某个类,例如 Base_class。多亏了这个宏,才能通过指向 Base_class 的指针正确地序列化 Derived_class 类型的类。

问题是: 我在 C++ 中用于编写通过从 std::string 到(指向)函数的映射实现的抽象工厂,该函数返回所需类型的对象(由于协变类型,一切都很好)。

悬停我看不到如何使用上述非虚拟序列化模板成员函数在不知道其类型的情况下正确反序列化(即构造)对象(但假设类型信息已由序列化程序存储,在字符串中说)。

我想做的(保持与上面相同的命名法)类似于以下内容:

XmlArchive xmlArchive; //A type or archive
xmlArchive.open("C:/ser.txt"); //Contains type information for the serialized class
Base_class* basePtr = Factory<Base_class>::create("derived_class",xmlArchive);

使用右侧的函数在 Derived_class 类型的堆上创建一个对象(通过默认构造函数,这是我知道如何解决的部分)并调用 xmlArchive 的序列化函数(我在这里卡住了!) ,即执行以下操作:

Base_class* Factory<Base_class>::create("derived_class",xmlArchive)

    Base_class* basePtr = new Base_class; //OK, doable, usual map string to pointer to function
    static_cast<Derived_class*>( basePtr )->serialize( xmlArchive ); //De-serialization, how?????
    return basePtr;

我确信这是可以做到的(boost serialize 可以做到,但它的代码是不可理解的!:P),但我不知道怎么做。 关键问题是序列化函数是一个模板函数。所以我不能有一个指向通用模板函数的指针。 由于编写模板化序列化函数的目的是使代码通用(即不必为不同的归档器重新编写序列化函数),因此必须为所有可能的归档类型注册所有派生类是没有意义的,比如:

MY_CLASS_REGISTER(Derived_class, XmlArchive);
MY_CLASS_REGISTER(Derived_class, TxtArchive);
...

事实上,在我的代码中,我依靠重载来获得正确的行为:

void serialize( XmlArchive& archive, Derived_class& derived );
void serialize( TxtArchive& archive, Derived_class& derived );
...

要记住的关键点是存档类型始终是已知的,即我从不对存档类使用运行时多态性...(我再次在存档类型上使用重载)。

有什么建议可以帮助我吗?

非常感谢您!

干杯

【问题讨论】:

【参考方案1】:

您只需要在存储派生类型的信息之前存储某种标识符。然后在阅读时使用您首先阅读的标识符将您引导到工厂,然后该工厂可以正确解释下一个信息块并生成您的派生类型。这可能是 boost::serialization 在非常基本的层面上所做的。也许是这样的:


ar >> type;
Base_class* basePtr = Factory<Base_class>::create(type,xmlArchive);

然后你有一个看起来像这样的对象地图:



struct reader_base virtual void load(xmlArchive, base_ptr) = 0; template < typename T > struct reader : reader_base virtual void load(xmlArchive, base_ptr) static_cast<T*>(base_ptr)->serialize(xmlArchive); ;

【讨论】:

但是我如何使这段代码对于 xmlArchive 的类型具有通用性?我希望每个派生类都有一个注册(宏作为 boost,或功能更好),并且每种类型的存档都能够仅根据此注册提供的信息来执行它需要的操作。无论如何感谢您的帮助! 只需将上述概念更改为基于存档的模板。没什么大不了的。【参考方案2】:

如果您的存档类型总是已知的,为什么还要在上面参数化您的 serialize 函数?有一个关于代码重用的论点,是的,但是如果你的 Archive 类改变了它的定义或被替换,你可能需要重构你的一些序列化代码。

如果你坚持:

class ToBeSerialized : public Base_class 
public:
    void serialize(Archive & ar)
    .....
;

然后您可以将指针指向您的 serialize 函数,并将其绑定到您的工厂。

您还需要为每个类绑定单独的create 函数,以便在需要时实例化正确的类型。比如:

template <typename T> Base_class* Factory::create(Archive& xmlArchive) 
    T* derivedPtr = new T;
    derivedPtr->serialize( xmlArchive );
    return derivedPtr;

然后工厂将需要一个通用的create 方法来调用正确的静态参数化create&lt;T&gt;

Base_class* Factory::create(const char* typeString, Archive& xmlArchive) 
    // Pseudocode.
    return m_map.find(typeString)->callCreate(xmlArchive);

【讨论】:

我遇到的问题正是归档类型是明确的但不是固定的,我可以说是 Xml 归档、二进制归档等等......这里的重点是序列化函数是一个相当愚蠢的例子: template void serialize( A& a ) serialize( a, m_int, "m_int" );序列化(一个,m_double,“m_double”);实际上,它只是列出了需要序列化的数据成员,并为每个成员关联了一个字符串。然后 serialize( a, m_int, "m_int" ) 根据 a (存档)和 m_int (比如要序列化的 int )的类型进行序列化。很抱歉造成混乱。

以上是关于C++ 模板工厂构造函数/反序列化的主要内容,如果未能解决你的问题,请参考以下文章

没有从字符串值反序列化的字符串参数构造函数/工厂方法('2018-12-14')

Jackson MismatchedInputException(没有从字符串值反序列化的字符串参数构造函数/工厂方法)

Spring Boot:没有从字符串值反序列化的字符串参数构造函数/工厂方法

使用 Jackson 从 XML 到 POJO 的反序列化问题:没有从字符串值反序列化的字符串参数构造函数/工厂方法

objectmapper.readValue() 失败并出现错误“没有从字符串值反序列化的字符串参数构造函数/工厂方法”

com.fasterxml.jackson.databind.JsonMappingException 没有从字符串值('1')反序列化的字符串参数构造函数/工厂方法