将派生 class 指针序列化为具有 boost::serialization 的向量时出现问题

Problem in serialization of derived class pointer into a vector with boost::serialization

我有一段代码,其中定义了以下 classes:BaseDerivedContaienr.

Derived显然继承了Base,而Container包含一个Base共享指针的向量,可以是Base和[=的指针18=].

我想序列化 Container,因此向量的元素分别序列化为 BaseDerived,但它似乎不起作用。

这是我的测试代码:

#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <sstream>

/// Base class ///
class Base {
public:

  using Ptr = std::shared_ptr<Base>;

public:

  void setDouble(double d) {
    m_d = d;
  }

  void setInteger(int c) {
    m_c = c;
  }

  double getDouble() const {
    return m_d;
  }

  int getInteger() const {
    return m_c;
  }

private:

  double m_d;
  int m_c;
};

/// Derived class from Base ///
class Derived : public Base {
public:

  using Ptr = std::shared_ptr<Derived>;

public:

  void setString(const std::string& s) {
    m_s = s;
  }

  const std::string& getString() const {
    return m_s;
  }

private:

  std::string m_s;
};

/// Container of base class pointers ///
class Container {
public:

  void addData(Base::Ptr data) {
    m_data.push_back(data);
  }

  const std::vector<Base::Ptr>& getDataVector() const {
    return m_data;
  }

private:

  std::vector<Base::Ptr> m_data;
};

BOOST_SERIALIZATION_SPLIT_FREE(Base)
BOOST_SERIALIZATION_SPLIT_FREE(Derived)
BOOST_SERIALIZATION_SPLIT_FREE(Container)
BOOST_CLASS_EXPORT_GUID(Derived, "Derived")

namespace boost {
namespace serialization {

/// Serialization of base class ///
template<class Archive>
void save(Archive& ar, const Base& m, unsigned int) {
  auto d = m.getDouble();
  auto i = m.getInteger();
  ar& make_nvp("doublevalue", d);
  ar& make_nvp("intvalue", i);
}

template<class Archive>
void load(Archive& ar, Base& m, unsigned int) {
  double d;
  int i;

  ar& make_nvp("doublevalue", d);
  ar& make_nvp("intvalue", i);

  m.setDouble(d);
  m.setInteger(i);
}

/// serialization of derived class ///
template<class Archive>
void save(Archive& ar, const Derived& m, unsigned int) {
  ar& make_nvp("base", base_object<const Base>(m));
  ar& make_nvp("stringvalue", m.getString());
}

template<class Archive>
void load(Archive& ar, Derived& m, unsigned int) {
  std::string s;

  ar& make_nvp("base", base_object<Base>(m));
  ar& make_nvp("stringvalue", s);

  m.setString(s);
}

/// serialization of container class ///
template<class Archive>
void save(Archive& ar, const Container& m, unsigned int) {
  ar& make_nvp("data", m.getDataVector());
}

template<class Archive>
void load(Archive& ar, Container& m, unsigned int) {
  std::vector<Base::Ptr> data;

  ar& make_nvp("data", data);

  for (const auto& it : data) {
    m.addData(it);
  }
}

}
} // namespace boost::serialization


int main(int argc, char *argv[]) {
  // Initialize container
  Container container;
  auto baseObj = std::make_shared<Base>();
  baseObj->setDouble(4.3);
  baseObj->setInteger(6);
  auto derivedObj = std::make_shared<Derived>();
  derivedObj->setDouble(1.1);
  derivedObj->setInteger(2);
  derivedObj->setString("string in derived");
  container.addData(baseObj);
  container.addData(derivedObj);

  // Print serialization of Base
  std::stringstream basess;
  boost::archive::xml_oarchive baseoa{basess};
  baseoa <<  boost::serialization::make_nvp("baseclass", baseObj);
  std::cout << basess.str() << std::endl;

  // Print serialization of Derived
  std::stringstream derivedss;
  boost::archive::xml_oarchive derivedoa{derivedss};
  derivedoa <<  boost::serialization::make_nvp("derivedclass", derivedObj);
  std::cout << derivedss.str() << std::endl;

  // Print serialization of Container
  std::stringstream containerss;
  boost::archive::xml_oarchive containeroa{containerss};
  containeroa <<  boost::serialization::make_nvp("containerclass", container);
  std::cout << containerss.str() << std::endl;
  return 0;
}

当我 运行 程序时,我打印 baseObj 的序列化,这是 Base:

的共享指针
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="17">
<baseclass class_id="0" tracking_level="0" version="1">
    <px class_id="1" tracking_level="1" version="0" object_id="_0">
        <doublevalue>4.29999999999999982e+00</doublevalue>
        <intvalue>6</intvalue>
    </px>
</baseclass>

这似乎是正确的,因为我在基础 class.

中同时定义了 doublevalueintvalue

然后我打印 derivedObj 的序列化,这是 Derived:

的共享指针
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="17">
<derivedclass class_id="0" tracking_level="0" version="1">
    <px class_id="1" tracking_level="1" version="0" object_id="_0">
        <base class_id="2" tracking_level="1" version="0" object_id="_1">
            <doublevalue>1.10000000000000009e+00</doublevalue>
            <intvalue>2</intvalue>
        </base>
        <stringvalue>string in derived</stringvalue>
    </px>
</derivedclass>

它似乎按预期工作,因为我有基础 class 数据,还有派生 class.

stringvalue

现在,如果我将两个指针都放入 Container 中的 std::vector<std::shared_ptr<Base>>,我希望能够正确地序列化 baseObjderivedObj。相反,这是输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="17">
<containerclass class_id="0" tracking_level="0" version="0">
    <data class_id="1" tracking_level="0" version="0">
        <count>2</count>
        <item_version>1</item_version>
        <item class_id="2" tracking_level="0" version="1">
            <px class_id="3" tracking_level="1" version="0" object_id="_0">
                <doublevalue>4.29999999999999982e+00</doublevalue>
                <intvalue>6</intvalue>
            </px>
        </item>
        <item>
            <px class_id_reference="3" object_id="_1">
                <doublevalue>1.10000000000000009e+00</doublevalue>
                <intvalue>2</intvalue>
            </px>
        </item>
    </data>
</containerclass>

vector 的两个元素都序列化为 Base 指针。

我尝试按照文档中的建议使用 BOOST_CLASS_EXPORT_GUID(Derived, "Derived") 宏,但它似乎不起作用。

我也尝试了 this post 中提出的解决方案,通过评论 BOOST_CLASS_EXPORT_GUID(Derived, "Derived") 并在 Container 的序列化中使用 register_type,但问题仍然存在:

/// serialization of container class ///
template<class Archive>
void save(Archive& ar, const Container& m, unsigned int) {
  ar.template register_type<Derived>();
  ar& make_nvp("data", m.getDataVector());
}

template<class Archive>
void load(Archive& ar, Container& m, unsigned int) {
  ar.template register_type<Derived>() ;
  std::vector<Base::Ptr> data;

  ar& make_nvp("data", data);

  for (const auto& it : data) {
    m.addData(it);
  }
}

如何正确序列化存储在 Base 的共享指针向量中的 Derived class?

问题的一部分可能是 std::shared_ptr 在派生 classes 的情况下的行为。因此,您有必要将 std::shared_ptr 替换为普通指针。

struct A
{

};

struct B : public A
{

};

void fun(const std::shared_ptr<A>& base)
{
    std::cout << typeid(base).name() << std::endl;
}

int main(int argc, char *argv[]) {
    auto a=std::make_shared<A>();
    auto b=std::make_shared<B>();

  std::cout << typeid(a).name() << std::endl;
  std::cout << typeid(b).name() << std::endl;
  fun(a);
  fun(b);
}

这给了你,你可能认为第二行和第四行是相等的:

class std::shared_ptr<struct A>
class std::shared_ptr<struct B>
class std::shared_ptr<struct A>
class std::shared_ptr<struct A>

第二个但不是很明显的一点是,你的基础 class 应该至少包含一个 virtual function。您可以通过包含以下内容使析构函数成为虚函数:

virtual ~Base() {};

文档说:

It turns out that the kind of object serialized depends upon whether the base class (base in this case) is polymophic or not. If base is not polymorphic, that is if it has no virtual functions, then an object of the type base will be serialized. Information in any derived classes will be lost. If this is what is desired (it usually isn't) then no other effort is required.

在我用普通指针替换所有 shared_ptr 并添加虚拟析构函数后,结果如我所愿,我获得了最后一部分的以下输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE boost_serialization>
<boost_serialization signature="serialization::archive" version="17">
<containerclass class_id="0" tracking_level="0" version="0">
        <data class_id="1" tracking_level="0" version="0">
                <count>2</count>
                <item_version>0</item_version>
                <item class_id="2" tracking_level="1" version="0" object_id="_0">
                        <doublevalue>4.29999999999999982e+00</doublevalue>
                        <intvalue>6</intvalue>
                </item>
                <item class_id="3" class_name="Derived" tracking_level="1" version="0" object_id="_1">
                        <base object_id="_2">
                                <doublevalue>1.10000000000000009e+00</doublevalue>
                                <intvalue>2</intvalue>
                        </base>
                        <stringvalue>string in derived</stringvalue>
                </item>
        </data>
</containerclass>