使用序列化 C++ 保存游戏状态

Posted

技术标签:

【中文标题】使用序列化 C++ 保存游戏状态【英文标题】:Saving a game state using serialization C++ 【发布时间】:2014-04-29 06:54:03 【问题描述】:

我有一个名为 Game 的类,其中包含以下内容:

vector<shared_ptr<A>> attr; // attributes
D diff; // differences
vector<shared_ptr<C>> change; // change

我的问题是,如何将这些(保存)写入文件并稍后读取/加载? 我考虑过使用带有这些的struct,并简单地保存struct,但我不知道从哪里开始。

这是我目前的尝试,只是尝试保存change。我已经阅读了很多关于这个问题的内容,我的问题(其中一个,无论如何)似乎是我存储的指针在关闭程序后将无效(再加上我也在退出之前释放它们的事实)。

/* Saves state to file */
void Game::saveGame(string toFile) 
    ofstream ofs(toFile, ios::binary);
    ofs.write((char *)&this->change, sizeof(C));

    /* Free memory code here */
    ....

    exit(0);
; 

/* Loads game state from file */
void Game::loadGame(string fromFile) 
    ifstream ifs(fromFile, ios::binary);
    ifs.read((char *)&this->change, sizeof(C));

    this->change.toString(); // display load results
;

谁能指导我正确的方向来序列化这些数据?我只想使用标准包,所以不要boost

谢谢。

【问题讨论】:

你有办法将单个 A 或 C 写入磁盘吗?也许你应该先解决这个问题。 您是否有理由使用指针使事情复杂化?在任何情况下,首先为单个AC 编写代码,然后存储元素计数,然后存储元素。要加载,请读取元素计数和循环。 【参考方案1】:

我不知道如何实现类ACD,但这是第一个问题:如何序列化该类的对象。对于C 的情况,您需要实现如下内容:

std::ostream& operator <<(std::ostream& os, const C& c) 
    // ... code to serialize c to an output stream
    return os;

std::istream& operator >>(std::istream& is, C& c) 
    // ... code to populate c contents from the input stream
    return is;

或者,如果您愿意,可以为该类创建一个 write()read() 函数。

好吧,如果你想序列化 vector&lt;shared_ptr&lt;C&gt;&gt; 看起来很明显你不想序列化指针,而是序列化内容。因此,您需要取消引用这些指针中的每一个并进行序列化。如果在加载之前不知道向量的大小(即,并不总是相同),则需要存储该信息。然后,您可以创建一对函数来序列化完整的向量:

std::ostream& operator <<(std::ostream& os, const std::vector<std::shared_ptr<C>>& vc) 
    // serialize the size of the vector using << operator
    // for each element of the vector, let it be called 'pc'
        os << *pc << std::endl; // store the element pointed by the pointer, not the pointer.
    return os;

std::istream& operator >>(std::istream& is, std::vector<std::shared_ptr<C>>& c) 
    // read the size of the vector using >> operator
    // set the size of the vector
    // for each i < sizeo of the vector, let 'auto &pc = vc[i]' be a reference to the i-th element of the vector
        C c;                         // temporary object
        is >> c;                     // read the object stored in the stream
        pc = std::make_shared<C>(c); // construct the shared pointer, assuming the class C has copy constructor

    return is;

然后,

/* Saves state to file */
void Game::saveGame(string toFile) 
    ofstream ofs(toFile);
    ofs << change;
    ....
; 

/* Loads game state from file */
void Game::loadGame(string fromFile) 
    ifstream ifs(fromFile);
    ifs >> change;
;

我知道你还有很多事情需要解决。我建议您进行调查以解决这些问题,以便您了解如何解决您的问题。

【讨论】:

【参考方案2】:

您不仅要保存指针,还要尝试保存 shared_ptr,但使用了错误的大小。

您需要为所有类编写序列化函数,注意不要只编写非 POD 类型的原始位。始终为所有内容实现逐个成员序列化是最安全的,因为您永远不知道未来会带来什么。 然后处理它们的集合只是存储有多少的问题。

Cs 的示例:

void Game::save(ofstream& stream, const C& data)

    // Save data as appropriate...


void Game::saveGame(string toFile) 
    ofstream ofs(toFile, ios::binary);

    ofs.write((char *)change.size(), sizeof(change.size());
    for (vector<shared_ptr<C>>::const_iterator c = change.begin(); c != change.end(); ++c)
    
        save(ofs, **c);
    
; 


shared_ptr<C> Game::loadC(ofstream& stream)

    shared_ptr<C> data(new C);
    // load the object...
    return data;


void Game::loadGame(string fromFile) 
    change.clear();
    size_t count = 0;
    ifstream ifs(fromFile, ios::binary);
    ifs.read((char *)&count, sizeof(count));
    change.reserve(count);
    for (int i = 0; i < count; ++i)
    
        change.push_back(loadC(ifs));
    
;

当然,所有的错误处理都丢失了——你需要添加它。

至少从文本存储开始(使用&lt;&lt;&gt;&gt;)而不是二进制文件实际上是个好主意。当您可以在文本编辑器中编辑它时,更容易找到错误或弄乱保存的状态。

【讨论】:

【参考方案3】:

编写自己的序列化是一个相当大的挑战。即使您不使用 boost serializatoin,我也建议您学习如何使用它并理解它是如何工作的,而不是自己发现它。

在序列化时,您最终会得到一个数据缓冲区,您对其中的内容有一个非常模糊的概念。您必须保存恢复它所需的一切。您逐块阅读它。示例(未编译,未测试且不时尚):

void save(ostream& out, const string& s)

   out << s.size();
   out.write(s.c_str(), s.size());

void load(istream& in, string& s)

   unsigned len;
   in >> len;
   s.resize(len);
   in.read((char*)s, len); 


struct Game

   void save(ostream& out)
   
      player.save(out);
   ;
   void load(istream& in)
   
      player.load(in);
   
;
struct Player

   void save(ostream& out)
   
       // save in the same order as loading, serializing everything you need to read it back
       save(out, name);
       save(out, experience);
   
   void load(istream& in)
   
       load(in, name);
       load(in, experience); // 
   
;

我不知道你为什么要对自己这样做而不是使用 boost,但这些是你应该考虑的一些情况: - 类型 - 你必须想办法知道你实际上有什么“变化类型”。 - 一个字符串(向量,随便) - 大小 + 数据(那么你从字符串中读回的第一件事就是长度,你调整它的大小并复制“长度”个字符) - 一个指针 - 保存指针指向的数据,然后在反序列化时,您必须分配它,构造它(通常是默认构造)并读回数据并将成员重置为各自的值。注意:您必须避免内存泄漏。 - 多态指针 - 哎呀你必须知道指针实际指向什么类型,你必须构造派生类型,保存派生类型的值......所以你必须保存类型信息 - 空指针...你必须区分空指针,这样你就知道你不需要进一步从流中读取数据。 - 版本控制 - 添加/删除字段后,您必须能够读取数据

内容太多,您无法获得完整的答案。

【讨论】:

以上是关于使用序列化 C++ 保存游戏状态的主要内容,如果未能解决你的问题,请参考以下文章

保存游戏状态 Android

可序列化的保存游戏功能不起作用[关闭]

Android可序列化问题

如何在pygame中保存/加载游戏功能?

Java基础序列化与反序列化深入分析

使用序列化保存对象状态到存储介质