C ++ сохранить и загрузить игру с указателями в структурах - PullRequest
5 голосов
/ 30 ноября 2010

Я знаю, что могу использовать:

MyGame  game; // the game object
//

ofstream out("mygame.bin", ios::binary);
out.write((char *)&game, sizeof(MyGame));

, чтобы сохранить и загрузить игру, но что если у меня есть указатели внутри структуры MyGame?Будут ли сохранены указатели, а не данные, на которые они указывают?

и: как это решить?

Ответы [ 9 ]

5 голосов
/ 30 ноября 2010

Вы не можете просто написать указатели на поток и ожидать, что это будет волшебным образом выполнено. Вам необходимо реализовать методы сохранения / загрузки в ваших объектах. Например:

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
2 голосов
/ 30 ноября 2010

Предполагая, что вы не переопределили приведение типа char *, да, это, скорее всего, сохранит только указатель, а не данные.

Что вам нужно, это сериализация вашего объекта.Вы можете предоставить метод для маршалинга состояния объекта в потоке битов и его записи.И вам также нужно иметь метод для восстановления состояния обратно.

Подробнее о сериализации вы можете прочитать в wikipedia

1 голос
/ 30 ноября 2010

Boost имеет библиотеку сериализации со встроенной поддержкой глубокого сохранения и восстановления указателей и правильной сериализации указателей на общие данные.

Это довольно обширная библиотека, но вам не нужно писать столько кода, чтобы начать использовать его в своих собственных проектах. На мой взгляд, стоит потраченных усилий на изучение чего угодно, кроме самых простых требований сериализации.

1 голос
/ 30 ноября 2010

Вы можете перегрузить оператор вывода потока (<<) и вывести каждое отдельное поле (и наоборот)

РЕДАКТИРОВАТЬ: вот полный пример ...

#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;
}

Вы должны понимать проблемы, связанные с этим подходом, хотя, например, полученный файл будет зависеть от платформы (т. Е. Непереносим) и т. Д.

0 голосов
/ 30 ноября 2010

Спасибо всем за быстрые и хорошие ответы, но мой друг (который помогает мне в этом) сказал мне, что мы должны сделать это по-другому.

Я просто сохраняю основы объекта, а остальные мы воссоздаем в функции.

Это карточная игра, и чтобы сохранить стопку карточек, мы будем сохранять только идентификатор карты (но не объекты) и просто заново инициализировать каждую карту, когда будем читать идентификатор из файла.

0 голосов
/ 30 ноября 2010

То, что вы сделали, это мелкая копия, если у вас есть указатели в вашем классе MyGame, то глубокая копия ОБЯЗАНА !.Я предлагаю реализовать функцию или набор функций внутри MyGame, которые позаботятся о сохранении своих собственных данных в файл, и вам нужно будет только вызвать их.

0 голосов
/ 30 ноября 2010

«Наивная» сериализация, которая просто сбрасывает значения указателей, никогда не сработает, потому что при десериализации эти указатели будут недействительными.

Общий подход к такой проблеме будет выглядеть следующим образом:

  1. Пусть каждый объект, который может быть сериализован в вашей игре, реализует виртуальную функцию serialize() (я предполагаю, что все такие объекты в конечном итоге будут происходить из одного базового класса).
  2. Имеютбазовый класс реализует публичную функцию get_serialized_id().Эта функция будет использовать статическую переменную с автоинкрементом для создания уникального идентификатора для каждого экземпляра объекта, но только при первом вызове (последующие вызовы просто вернут существующее значение).

Теперь,при сериализации:

  1. Начните с std::map<int, YourBaseClass*>.Добавьте объект game к этой карте, используя значение, возвращаемое get_serialized_id() для ключа.
  2. Пока карта содержит объекты, которые еще не были сериализованы:
    • Возьмите первый такойobject.
    • Сериализует его get_serialized_id().
    • Сериализует его, вызывая его реализацию для serialize().Пусть он сохранит свои примитивные данные как обычно.Для получения данных, доступных через указатели, вызовите get_serialized_id() для каждого указанного объекта и просто сериализуйте возвращаемый из него номер.Кроме того, добавьте этот объект на карту.

Это приведет к сериализации объектов (в «случайном» порядке) вместе со «случайным» идентификатором каждого из них..

При десериализации:

  1. Начните с std::map<int, YourBaseClass*>.Прочитайте первый элемент в вашем сохраненном файле.
  2. Для каждого объекта, на который указывает этот первый объект, вы знаете уникальный идентификатор (это то, что вы сериализовали вместо указателя).Извлечь элемент с этим идентификатором из сохраненного файла и десериализовать его.
  3. Рекурсивно делать это, пока все элементы не будут извлечены и десериализованы из сохраненного файла.
  4. Поскольку каждый элемент имеет все свои десериализованные зависимостина шаге 3 выше создайте его экземпляр как объект и добавьте его на карту.
  5. Это позволит вам получить указатель с карты, учитывая идентификатор элемента, который теперь можно использовать для установки членов-указателейобъекты, зависящие от этого предмета.
  6. Когда рекурсия закончится, последним объектом на карте станет ваш главный "игровой" объект со всеми указателями, готовыми к работе.
0 голосов
/ 30 ноября 2010

Создайте функцию сериализации для каждого типа, которая должна быть постоянной.
Вызовите ее для каждого члена.

На самом деле это похоже на сериализацию по сети или визуализацию для целей отладки.

boost.serialize может помочь вам.

0 голосов
/ 30 ноября 2010

Попробуйте game.serialize(out);.В вашей функции serialize вызывайте serialize членов вашего указателя.

...