简体   繁体   中英

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

I've a piece of code where I define following classes: Base , Derived , Contaienr .

Derived obviously inherits Base , while Container contains a vector of Base shared pointers, that can be pointers of both Base and Derived .

I want to serialize Container so elements of vectors are serialized to Base and Derived respectly, but it doen't seem to work.

This is my test code:

#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;
}

When I run the program I print the serialization of baseObj that's a shared pointer of 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>

It seems correct since I've both doublevalue and intvalue defined in the base class.

Then I print the serialization of derivedObj that's a shared pointer of 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>

It seems to work as expected, since I've the base class data, and also stringvalue of the derived class.

Now, if I put both pointers into a std::vector<std::shared_ptr<Base>> in Container , I was expecting to serialize both baseObj and derivedObj correctly. Instead this is the output:

<?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>

Both elements of vector are serialized as Base pointers.

I've tried to use the BOOST_CLASS_EXPORT_GUID(Derived, "Derived") macro as suggested in documentation, but it does not seem to work.

I've also tried the solution proposed in this post , by commenting the BOOST_CLASS_EXPORT_GUID(Derived, "Derived") and using register_type in the serialization of Container , but problem remains:

/// 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);
  }
}

How can I serialize correctly the Derived class stored in a vector of shared pointers of Base ?

A part of the problem might be the behaviour of std::shared_ptr in case of derived classes. So it is necessary for you to replace the std::shared_ptr with just a normal pointer.

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);
}

This gives you, where you might expect that the second and forth line are equal:

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

A second but not so obvious point is, that your base class should contain at least one virtual function . You can make the destructor being a virtual function by just including:

virtual ~Base() {};

The documentation says:

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.

After I replaced all shared_ptr by plain pointers and adding the virtual destructor the outcome was as desired and I obtain the following output for the last portion:

<?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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM