To handle backward compatibility for serialized game save files using cereal's versioning features:
Step 1: Define a version number for your serializable classes and increment it whenever you make changes that affect serialization:
class Player {
// ...
};
CEREAL_CLASS_VERSION(Player, 1);
class GameState {
// ...
};
CEREAL_CLASS_VERSION(GameState, 2);
Step 2: In your serialization functions, use the versioned save
/load
 or serialize
 function signatures:
template <class Archive>
void Player::serialize(Archive& ar,
std::uint32_t version) {
ar(name, level);
if (version >= 1) {
ar(health);
}
}
template <class Archive>
void GameState::serialize(Archive& ar,
std::uint32_t version) {
ar(players, score);
if (version >= 2) {
ar(difficulty);
}
}
Step 3: When deserializing, check the version number and handle missing or added fields accordingly. Provide default values for fields that may not exist in older save files:
template <class Archive>
void Player::serialize(Archive& ar,
std::uint32_t version) {
ar(name, level);
if (version >= 1) {
ar(health);
} else {
// Default value for older versions
health = 100;
}
}
template <class Archive>
void GameState::serialize(Archive& ar,
std::uint32_t version) {
ar(players, score);
if (version >= 2) {
ar(difficulty);
} else {
// Default value for older versions
difficulty = 1;
}
}
Step 4: When loading a save file, cereal will automatically detect and pass the appropriate version number to your serialization functions. Your deserialization logic will handle the version differences.
By following this approach, newer versions of your game will be able to load and handle save files from older versions gracefully. Remember to increment the version number whenever you make changes to the serialization layout of your classes.
A complete example is provided below:
#include <cereal/archives/binary.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
#include <fstream>
#include <iostream>
#include <vector>
class Player {
public:
std::string name;
int level;
int health;
private:
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar,
std::uint32_t version) {
ar(name, level);
if (version >= 1) {
ar(health);
} else {
// Default value for older versions
health = 100;
}
}
};
CEREAL_CLASS_VERSION(Player, 1);
class GameState {
public:
std::vector<Player> players;
int score;
int difficulty;
private:
friend class cereal::access;
template <class Archive>
void serialize(Archive& ar,
std::uint32_t version) {
ar(players, score);
if (version >= 2) {
ar(difficulty);
} else {
// Default value for older versions
difficulty = 1;
}
}
};
CEREAL_CLASS_VERSION(GameState, 2);
int main() {
GameState gameState;
gameState.players.emplace_back(Player{
"Alice", 10, 80});
gameState.players.emplace_back(Player{
"Bob", 20, 70});
gameState.score = 1000;
gameState.difficulty = 2;
{
std::ofstream file("savegame.dat");
cereal::BinaryOutputArchive archive(file);
archive(gameState);
}
GameState loadedGameState;
{
std::ifstream file("savegame.dat");
cereal::BinaryInputArchive archive(file);
archive(loadedGameState);
}
std::cout << "Loaded Game State:\n";
std::cout << "Score: "
<< loadedGameState.score << "\n";
std::cout << "Difficulty: "
<< loadedGameState.difficulty << "\n";
std::cout << "Players:\n";
for (const auto& player :
loadedGameState.players) {
std::cout << " " << player.name
<< " (Level " << player.level
<< ", Health " << player.health << ")\n";
}
}
Loaded Game State:
Score: 1000
Difficulty: 2
Players:
Alice (Level 10, Health 80)
Bob (Level 20, Health 70)
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.