Проблема в сериализации указателя производного класса в вектор с boost :: serialization - PullRequest
3 голосов
/ 11 июля 2019

У меня есть фрагмент кода, в котором я определяю следующие классы: Base, Derived, Contaienr.

Derived, очевидно, наследует Base, тогда как Container содержит векториз Base общих указателей, которые могут быть указателями как Base, так и Derived.

Я хочу сериализовать Container, чтобы элементы векторов сериализовались в Base и Derived соответственно,но, похоже, это не работает.

Это мой тестовый код:

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

Кажется правильным, поскольку в базовом классе определены и doublevalue, и intvalue.

Затем я печатаю сериализацию 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>

Кажется, что он работает как ожидалось, поскольку у меня есть данные базового класса, а также stringvalue производного класса.

Теперь, еслиЯ поместил оба указателя в std::vector<std::shared_ptr<Base>> в Container, я ожидал правильно сериализовать оба baseObj и derivedObj.Вместо этого это вывод:

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

Оба элемента вектора сериализуются как Base указатели.

Я пытался использовать макрос BOOST_CLASS_EXPORT_GUID(Derived, "Derived"), как предложено в документации, ноКажется, он не работает.

Я также попробовал решение, предложенное в этой публикации , комментируя BOOST_CLASS_EXPORT_GUID(Derived, "Derived") и используя register_type в сериализации Container,но проблема остается:

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

Как правильно сериализовать класс Derived, хранящийся в векторе общих указателей Base?

1 Ответ

1 голос
/ 11 июля 2019

Частично проблема может быть в поведении std::shared_ptr в случае производных классов. Поэтому вам необходимо заменить 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>

Второй, но не очень очевидный момент заключается в том, что ваш базовый класс должен содержать хотя бы одну виртуальную функцию . Вы можете сделать деструктор виртуальной функцией, просто включив:

virtual ~Base() {};

Документация гласит:

Оказывается, что сериализованный тип объекта зависит от того, является ли базовый класс (в данном случае базовым) полимофическим или нет. Если база не является полиморфной, то есть если у нее нет виртуальных функций, то объект типа base будет сериализован. Информация в любых производных классах будет потеряна. Если это то, что является желательным (обычно это не так), то никаких других усилий не требуется.

После того, как я заменил все 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>
...