开发者

c++ save and load game with pointers in structs

开发者 https://www.devze.com 2023-01-28 16:56 出处:网络
I know I can use: MyGamegame; // the game object // ofstream out(\"m开发者_运维百科ygame.bin\", ios::binary);

I know I can use:

MyGame  game; // the game object
//

ofstream out("m开发者_运维百科ygame.bin", ios::binary);
out.write((char *)&game, sizeof(MyGame));

to save and load the game, but what if I have pointers inside MyGame structure? will the pointers just be saved but not the data it points to?

and: how to solve this?


You can't just write pointers to a stream and expect it to be magically done. You need to implement save/load methods in your objects. E.g:

class Serializable
{
    virtual void save(std::ofstream& _out) const = 0;
    virtual void load(std::ifstream& _in) = 0;
}; // eo class Serializable


// some game object
class MyObject : public Serializable
{
    int myInt;
    std::string myString;

    virtual void save(std::ofstream& _out) const
    {
        _out << myInt << myString;
    }; // eo save

    virtual void load(std::ifstream& _in)
    {
        _in >> myInt >> myString;
    }; // eo load
}; // eo class SomeObject

class MyGame : public Serializable
{
    MyObject a;
    MyObject b;

    virtual void save(std::ofstream& _out) const
    {
        a.save(_out);
        b.save(_out);
    };  // eo save

    virtual void load(std::ifstream& _in)
    {
        a.load(_in);
        b.load(_in);
    };  // eo load
}; // eo class MyGame


Assuming you have not overridden char * cast, yes this will most probably save only pointer and not data.

What you need is Serialization of your object. You can provide a method to marshal the state of object in a bit stream and write that out. And you also need to have method to restore the state back.

You may read more about serialization on wikipedia


Boost has a serialization library, with built in support for deep pointer save and restore, and proper serialization of pointers to shared data.

It's a rather extensive library, but you don't need to write that much code to start using it in your own projects. Well worth the learning effort for anything but the simplest serialization requirements in my opinion.


You could overload the stream out operator (<<) and stream out each individual field (and vice versa)

EDIT: here is a complete example...

#include <iostream>
#include <fstream>
#include <map>

using namespace std;

template <typename T>
void serialize(ostream& str, const T& field)
{
  str.rdbuf()->sputn(reinterpret_cast<const char*>(&field), sizeof(T));
}

template <typename T>
void deserialize(istream& str, T& field)
{
  str.rdbuf()->sgetn(reinterpret_cast<char*>(&field), sizeof(T));
}

class MyGame
{
public:
 MyGame() : a(), b() {}
 MyGame(int av, int bv) : a(av), b(bv) {}

 friend ostream& operator<<(ostream& str, MyGame const& game);
 friend istream& operator>>(istream& str, MyGame& game);

  int getA() const { return a; }
  int getB() const { return b; }

private:
 int a;
 int b;
};

ostream& operator<<(ostream& str, MyGame const& game)
{
  serialize(str, game.a);
  serialize(str, game.b);
  return str;
}

istream& operator>>(istream& str, MyGame& game)
{
  deserialize(str, game.a);
  deserialize(str, game.b);
  return str;
}

int main(void)
{
  {
    ofstream fout("test.bin", ios::binary);
    MyGame game(10, 11);
    fout << game;
  }

  {
    ifstream fin("test.bin", ios::binary);
    MyGame game;
    fin >> game;
    cout << "game.a: " << game.getA() << ", game.b: " << game.getB() << endl;
  }

  return 0;
}

You must understand the issues with this approach though, such as the resulting file will be platform specific (i.e. non-portable) etc.


Try game.serialize(out);. In your serialize member function call serialize of your pointer members.


Make a serializing function per type that needs to be persistent.
Call this for each member.

It is actually similar to serializing over network or visualizing for debug-purposes.

boost.serialize can help you.


"Naive" serialization that just dumps the value of pointers is never going to work because, when deserializing, those pointers will be invalid.

The general approach to this kind of problem would go like this:

  1. Have each object that can be serialized in your game implement a serialize() virtual function (I am assuming all such objects will ultimately derive from the same base class).
  2. Have the base class implement a get_serialized_id() public function. This function will use a static auto-incremented variable to generate a unique id for each object instance, but only the first time it is called (subsequent calls will just return the existing value).

Now, when serializing:

  1. Start with a std::map<int, YourBaseClass*>. Add your game object to this map, using the value returned by get_serialized_id() for the key.
  2. While the map contains objects that have not been serialized yet:
    • Take the first such object.
    • Serialize its get_serialized_id().
    • Serialize it by calling its implementation for serialize(). Have it persist its primitive data as usual. For data available through pointers, call get_serialized_id() on each object pointed to and just serialize the number returned from it. Also, add that object to the map.

This will result in a bunch of objects being serialized (in a "random" order) along with each one's "random" id.

When deserializing:

  1. Start with a std::map<int, YourBaseClass*>. Read the first item in your saved file.
  2. For each object pointed to by this first object, you know a unique id (this is what you serialized instead of a pointer). Fetch the item with this id from the saved file and deserialize it.
  3. Recursively do this until all items have been fetched and deserialized from the saved file.
  4. As each item has all its dependences deserialized in step 3 above, instantiate it as an object and add it to the map.
  5. This will enable you to grab a pointer from the map given an item's id, which you can now use to set the pointer members of objects dependent on this item.
  6. When the recursion ends, the last object in the map will be your main "game" object with all its pointers ready to go.


What you did is shallow copy, if you have pointers in your MyGame class, then a deep copy is a MUST!. I suggest implementing a function or a set of functiions inside MyGame that will take care of saving its own data to a file,and you will only need to call it.


Thanks everyone for the fast and good answers, but a buddy of mine (who is helping me on this) told me we should do it in another way.

I just save the basics of the object and we recreate the rest in a function.

It's a card-game and to save the stack of cards we'll be saving the ID of the card only (not the objects) and just re-initializing each card when we read in the ID from the file.

0

精彩评论

暂无评论...
验证码 换一张
取 消