FAQ:STL:C++ сериализация данных

Материал из Весельчак У
Перейти к: навигация, поиск

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

  • Выравнивание членов касса или структуры, загрузить класс в другой библиотеке, где выравнивание отключено уже не получится
  • В гетерогенных средах порядок байт может другим: x86 и PowerPC
  • Нельзя сохранять, что-то кроме POD структур память на которую ссылаются указатели, таким образом не скопируем.
  • Нельзя сохранить в Humanreadable форме.

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

Например:

// вывести состояние классы и всех его членов.
std::cout << myClass;
// загрузить состояние класса из XML
myXML >> myClass;

Простой способ добиться результата это использовать велосипед написанный другими это не значит, что вам ни придётся ничего делать и получится всё само. Велосипед возьмём хороший много функциональный умеет сериализовывать и десериализовывать: контейнеры, классы, указатели, ссылки. Также сохранять в различных форматах: бинарный, текст, XML. Для продвинутых пользователей может сохранять не только в поток, но и куда угодно, например, в вектор или в сокет или выкинуть в пропасть.

Описание велосипеда: http://www.boost.org/doc/libs/1_36_0/libs/serialization/doc/index.html

Вот пример использования (взято из описания):

/////////////////////////////////////////////////////////////
// gps координаты
//
// illustrates serialization for a simple type
//
class gps_position
{
private:
    friend class boost::serialization::access;
    // When the class Archive corresponds to an output archive, the
    // & operator is defined similar to <<.  Likewise, when the class Archive
    // is a type of input archive the & operator is defined similar to >>.
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;
public:
    gps_position(){};
    gps_position(int d, int m, float s) :
        degrees(d), minutes(m), seconds(s)
    {}
};
 
int main() {
    // create and open a character archive for output
    std::ofstream ofs("filename");
 
    // create class instance
    const gps_position g(35, 59, 24.567f);
 
    // save data to archive
    {
        boost::archive::text_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
    	// archive and stream closed when destructors are called
    }
 
    // ... some time later restore the class instance to its orginal state
    gps_position newg;
    {
        // create and open an archive for input
        std::ifstream ifs("filename");
        boost::archive::text_iarchive ia(ifs);
        // read class state from archive
        ia >> newg;
        // archive and stream closed when destructors are called
    }
    return 0;
}

Для поддержки в наших классах мы должны в них реализовать метод serialize. Что он делает он в сериализатор(в терминах boost это archive) передаёт все внутренние данные, в свою очередь архив для простых типов делает копирование в памяти (кстати следит при этом за порядком байт, так что можно спокойно переносить на файл на другую машину с другой архитектурой), для сложных зовёт метод serialize, для стандартных контейнеров тоже, есть методы сериализации для контейнеров простых типов происходит копирование элементов, для контейнеров сложных типов происходит вызов serialize для каждого элемента. Для сериализации контейнеров придётся подключить дополнительные заголовки.

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

Но за универсальность boost::serialization придётся заплатить:

  • Время компиляции шаблоны могут разворачиваться довольно долго
  • Скорость: стек вызовов для сериализации какой-нибуть не слишком больной структурки может быть просто ужасающим вызовов 20-30.

Но, если вы не пишете систему массового обслуживания, то это то, что вам нужно с помощью этой библиотеки можно даже реализовать маршалинг или RPC, как больше нравится, т.е. я в своей программе создаю класс, но вместо собственно класса получаю gate к этому классу созданному на другой машине в момент когда я его создал на своей и я могу работать с созданным на другой машине классом, как с созданным на своей, при этом спокойно пользуюсь вызовами с передачей параметров по ссылке или указателю и знаю, что всё будет сериализовано - передано - обработано - передано обратно - десериализовано, так, что я не замечу подмены.


Понятно, как примерно работает сериализация и десериализация, и если понадобится можно реализовать свою сериализацию.


Можно, например, реализовать у объектов метод ToString который будет звать такой же метод у всех своих потомком, что бы сериализовать объект в строку.


Вот пример своей реализации архива, который я использую вместо бустового (ибо скорость ОЧЕНЬ важна, а универсальность не очень, но для маршалинга хватает), но делал так, что бы можно было использовать один вместо другого без переделки кода:

		class binary_iarchive
		{
		public:
			typedef serialization::container container;
			typedef container::iterator iterator;
 
			container &cont_;
			size_t currentPos_;
			typedef boost::mpl::bool_<false> is_saving;
			binary_iarchive(container & cont, long = 0)
					: cont_(cont)
					, currentPos_(0)
			{
			}
 
			template<typename T>
			binary_iarchive & operator>>(T & val)
			{
				deserialize_impl(val);
				return *this;
			}
 
			void reset()
			{
				resetPos();
				cont_.clear();
			}
 
			template<typename T>
			inline void raw_read(T beginPos, size_t len)
			{
				if (static_cast<size_t>(cont_.size() - currentPos_) < len)
					throw std::runtime_error("No more data");
 
				iterator pos = cont_.begin() + currentPos_;
				iterator endPos = pos + len;
				std::copy(pos, endPos, beginPos);
				currentPos_ = currentPos_ + len;
			}
		private:
			// Fundamental
			template<typename T>
			inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_fundamental<T> >::type* dummy = 0)
			{
				raw_read(reinterpret_cast<char*>(&val), sizeof(T));
			}
 
			//Classes
			template<typename T>
			inline void deserialize_impl(T & val, typename boost::enable_if<boost::is_class<T> >::type* dummy = 0)
			{
				deserialize_class(*this, val);
			}
		};