I am using the cereal library. The idea is to have a base class that serializes itself, and the derived class would serialize itself, and the base class. This is an example almost verbatim from the library documentation, but it doesn't work the same way the documentation claims - only the base serialize is ever called.

#include <cereal/archives/json.hpp>
#include <cereal/types/polymorphic.hpp>
#include <fstream>

struct EmptyBase
{
    public:
      virtual void foo() = 0;
      std::string name;
      template <class Archive>
      void serialize( Archive & ar )
      {
        std::cout << "Base serialize" << std::endl;
        ar(name);
      }
  
    void SaveToFile(const std::string& filename) const 
    {
        std::ofstream os(filename);
        cereal::JSONOutputArchive ar(os);
        ar(cereal::make_nvp("settings", *this));
    }

    void LoadFromFile(const std::string& filename) 
    {
        std::ifstream is(filename);
        cereal::JSONInputArchive ar(is);
        ar(cereal::make_nvp("settings", *this));
    }

    

};

struct DerivedTwo: EmptyBase
{
  public:

  void foo() {}

  std::string why;

  template <class Archive>
  void serialize( Archive & ar )
  {
    std::cout << "Child serialize" << std::endl;
    ar(cereal::base_class<EmptyBase>(this));
    ar( why );
  }
};

CEREAL_REGISTER_TYPE(DerivedTwo)
CEREAL_REGISTER_POLYMORPHIC_RELATION(EmptyBase, DerivedTwo)


int main()
{
    DerivedTwo myclass;
    myclass.name = "test";
    myclass.why = "because";
    myclass.SaveToFile("settings.json");
}

Expected output:

Child serialize
Base serialize

Actual output:

Base serialize

I found some similar problems, but their code seems to work this way. What am I missing?

Edit: I want to have a base class with Save() Load() and serialize(). Save()/Load() will de-/serialize the class into a JSON file.

I want to inherit from this class, and use it like this:

DerivedClassInstance.name = "test"; //this property is in the base class
DerivedClassInstance.y = "why"; //this is in the derived class

DerivedClassInstance.Save(); //This will save both y and name into a file in json format

DerivedClassInstance.Load(); //This will load the JSON from the file and populate name and y. 
//Both Save() and Load() are in the base class

I hope that makes sense.

2

There are 2 best solutions below

5
kiner_shah On

You have to make your SaveToFile and LoadFromFile functions pure virtual and implement them in derived classes:

#include <cereal/archives/json.hpp>
#include <cereal/types/polymorphic.hpp>
#include <fstream>
#include <memory>

struct EmptyBase
{
    virtual void foo() = 0;
    std::string name;

    template <class Archive>
    void serialize( Archive & ar )
    {
        std::cout << "Base serialize" << std::endl;
        ar(CEREAL_NVP(name));
    }
  
    virtual void SaveToFile(const std::string& filename) const = 0;

    virtual void LoadFromFile(const std::string& filename) = 0;
};

struct DerivedTwo : EmptyBase
{
  void foo() override {}

  std::string why;

  template <class Archive>
  void serialize( Archive & ar )
  {
    std::cout << "Child serialize" << std::endl;
    ar(cereal::base_class<EmptyBase>(this));
    ar( why );
  }

    void SaveToFile(const std::string& filename) const override
    {
        std::ofstream os(filename);
        cereal::JSONOutputArchive ar(os);
        ar(cereal::make_nvp("settings", *this));
        std::cout << (this) << '\n';
    }


    void LoadFromFile(const std::string& filename) override
    {
        std::ifstream is(filename);
        cereal::JSONInputArchive ar(is);
        ar(cereal::make_nvp("settings", *this));
    }
};

// You don't need this - works without this
//CEREAL_REGISTER_TYPE(DerivedTwo)
//CEREAL_REGISTER_POLYMORPHIC_RELATION(EmptyBase, DerivedTwo)

int main()
{
    DerivedTwo myclass;
    myclass.name = "test";
    myclass.why = "because";
    std::cout << (&myclass) << '\n';
    myclass.SaveToFile("settings.json");
    std::shared_ptr<EmptyBase> ptr = std::make_shared<DerivedTwo>();
    ptr->LoadFromFile("settings.json");
    std::cout << dynamic_cast<DerivedTwo*>(ptr.get())->why;
}

Godbolt link: https://godbolt.org/z/xfK148P81

https://uscilab.github.io/cereal/polymorphism.html - Refer to example in Registering from a header file section

3
n. m. could be an AI On

From the documentation:

cereal supports serializing smart pointers to polymorphic base classes and will automatically deduce the derived types at runtime.

There is no word on references or raw pointers, and this is indeed not supported. (Cereal does not support serialising raw pointers at all).

You can work around this by creating a temporary unique_ptr inside your load and save functions, and releasing it after (de)serialising is done, but you should ask yourself whether you really should. If you don't need pointers at all, write SaveToFile and LoadFromFile as standalone (non-member) function templates, templated by the type of the object being loaded/saved.