To correctly serialize and deserialize a std::map
where the value type is a base class pointer (Character*
) pointing to derived class objects (Monster
), follow these steps:
Step One: Include the necessary cereal headers:
#include <cereal/archives/binary.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/polymorphic.hpp>
Step Two: In your derived Monster
 class, ensure you have the serialize
 function defined and register the polymorphic relationship:
class Monster : public Character {
// ...
template <class Archive>
void serialize(Archive& ar) {
ar(cereal::base_class<Character>(this));
// ...
}
};
CEREAL_REGISTER_TYPE(Monster);
CEREAL_REGISTER_POLYMORPHIC_RELATION(Character, Monster);
Step Three: Create your map with base pointers to derived objects:
std::map<int, std::unique_ptr<Character>> enemies;
enemies[1] = std::make_unique<Monster>();
enemies[2] = std::make_unique<Monster>();
Step Four: Serialize the map using a cereal output archive:
std::ofstream file("enemies.dat");
cereal::BinaryOutputArchive archive(file);
archive(enemies);
Step Five: Deserialize the map using a cereal input archive:
std::ifstream file("enemies.dat");
cereal::BinaryInputArchive archive(file);
std::map<int, std::unique_ptr<Character>> loadedEnemies;
archive(loadedEnemies);
After deserialization, loadedEnemies
will contain pointers to the correct derived Monster
objects, even though the map's value type is std::unique_ptr<Character>
. Cereal's polymorphic serialization handles the type restoration.
Note: Make sure to use smart pointers like std::unique_ptr
in the map to avoid manual memory management and resource leaks.
A complete example is below:
#include <cereal/archives/binary.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/polymorphic.hpp>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
class Character {
public:
virtual ~Character() = default;
std::string name;
protected:
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar) {
ar(name);
}
};
class Monster : public Character {
public:
int health;
private:
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar) {
ar(cereal::base_class<
Character>(this), health);
}
};
CEREAL_REGISTER_TYPE(Monster);
CEREAL_REGISTER_POLYMORPHIC_RELATION(Character,
Monster);
int main() {
std::map<int, std::unique_ptr<
Character>> enemies;
enemies[1] = std::make_unique<Monster>();
enemies[1]->name = "Goblin";
dynamic_cast<Monster*>(
enemies[1].get())->health = 50;
enemies[2] = std::make_unique<Monster>();
enemies[2]->name = "Orc";
dynamic_cast<Monster*>(
enemies[2].get())->health = 100;
{
std::ofstream file("enemies.dat");
cereal::BinaryOutputArchive archive(file);
archive(enemies);
}
std::map<int, std::unique_ptr<
Character>> loadedEnemies;
{
std::ifstream file("enemies.dat");
cereal::BinaryInputArchive archive(file);
archive(loadedEnemies);
}
for (const auto& [id, enemy] : loadedEnemies) {
std::cout << "ID: " << id << ", Name: "
<< enemy->name;
if (auto monster = dynamic_cast<Monster*>(
enemy.get())) {
std::cout << ", Health: "
<< monster->health;
}
std::cout << "\n";
}
}
ID: 1, Name: Goblin, Health: 50
ID: 2, Name: Orc, Health: 100
Answers to questions are automatically generated and may not have been reviewed.
A detailed and practical tutorial for binary serialization in modern C++ using the cereal
library.