Методы сохранения сложных объектов в C ++ - PullRequest
4 голосов
/ 07 марта 2012

Я всегда сохранял данные, записывая ASCII в файлы, то есть

param1 = value1
param2 = string string string

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

class Record {
    int par1;
    string par2;
    vector<string> par3;
    void saveRecord(string fName);
    void loadRecord(string fName);
}

Record::saveRecord() {
    ...
    fstream outFile(fName.c_str(), fstream::out | fstream::binary);
    outFile.write( (char*)this, sizeof(Record) );
    outFile.close();
}

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

Похоже, что параметры

A) Напишите сложные алгоритмы сериализации, чтобы преобразовать каждый сложный тип данных в примитивы, а затем сохранить в двоичный файл; или

B) Просто запишите все в файл ASCII в соответствии с моей первоначальной стратегией.

Первый метод кажется слишком сложным, а второй - недостаточно элегантным.

Есть ли другие варианты? Есть ли стандартная процедура?

Примечание: я видел библиотеку boost :: serialization, которая также выглядит очень не элегантной и странно громоздкой, то есть вместо этого я просто написал бы свои собственные методы сериализации, если бы это была правильная методология.

Ответы [ 4 ]

3 голосов
/ 07 марта 2012

Нет.Используйте Boost.Serialization или Google Protocol Buffers.И да, вы должны написать функции, которые будут помещать и извлекать ваши данные в / из контейнера сериализации.Вот как это делается для надежных решений, которые, как ожидается, действительно будут работать.

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

Это может работать для простых вещей, но сломается так быстро, что вы просто пожалеете, что не сделали это с самого начала.

2 голосов
/ 07 марта 2012

2 стратегии, которые вы упомянули

A) Напишите сложные алгоритмы сериализации, чтобы преобразовать каждый сложный тип данных в примитивы, а затем сохранить в двоичный файл; или

B) Просто запишите все в файл ASCII в соответствии с моей первоначальной стратегией.

Как это обычно делается. По сути, вы создаете свой собственный формат файла. Наиболее распространенной парадигмой является парадигма чанка. Когда вы сохраняете объект или набор объектов, сначала вы пишете int, представляющий размер объекта или «порцию» данных. И следующий int, представляющий вид объекта. Возможно, вы также захотите включить информацию о версии, если вы заботитесь о поддержке конфигураций, которые вы сохраняете, когда пользователь обновляет свое программное обеспечение.

Опция A полезна, когда вы заботитесь о том, чтобы данные были очень точными, и облегчает проблемную загрузку / сохранение в c ++. Например. Сохраненные таким образом поплавки будут загружены с тем же значением, что и сохраненные.

Опция B полезна, когда вы хотите увидеть, что вы сохраняете, и, возможно, для того, чтобы человек каким-то образом изменил данные вручную. Поплавки, сохраненные здесь, при загрузке не будут точно такими же.

Попробуйте посмотреть другие форматы файлов для примера. Формат файла Midi использует парадигму чанков, а также имеет функцию потоковой передачи, которая использует опцию A. Формат файла obfront Wavefront используется в 3D-приложении, для простоты которого используется опция B. Все читается в вашем любимом текстовом редакторе.

1 голос
/ 07 марта 2012

Если вы хотите придерживаться сериализации на основе текста, вы можете попытаться просто переопределить:

  • std::ostream& operator <<(std::ostream& os, const Type& obj); для сериализации и
  • std::istream& operator >>(std::istream& is, Type& obj); для десериализации.

Библиотека уже сериализует и десериализует примитивные типы, вам не нужен доступ к классу или внутренностям шаблона для написания ваших собственных переопределений, и программисты на C ++ уже знакомы с этой концепцией.

Например, сериализатор / десериализатор для std::vector может выглядеть примерно так:

template<class T, class Alloc>
std::ostream& operator <<(std::ostream& os, const std::vector<T, Alloc>& vec)
{   os << vec.size << '\n';
    for(std::vector<T, Alloc>::const_iterator i = vec.begin();
        i != vec.end(); ++i)
        os << *vec << '\n';
    return os;
}

template<class T, class Alloc>
std::istream& operator >>(std::istream& is, std::vector<T, Alloc>& vec)
{   vec.clear();
    size_t size = 0;
    is >> size;
    vec.reserve(size);
    while(size--)
    {   T temp;
        is >> temp;
        vec.push_back(temp);
    }
    return is;
}

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

0 голосов
/ 07 марта 2012

Мне не известны какие-либо стандартные процедуры, это зависит от данных, которые вы храните. То, что вы описываете, - это разница между мелким и глубоким представлением (очень похоже на мелкое или глубокое копирование). Я бы предложил планирование упрощенного механизма сериализации для каждого класса в зависимости от того, что хранится. Как вы указали, недостаточно просто записать байтовое представление памяти, когда у вас есть базовые элементы, которые не соприкасаются (или даже не являются частью) с экземпляром класса.

...