Ways to save complex objects in C ++

I always saved data by writing ASCII to files, i.e.

param1 = value1 param2 = string string string 

and downloaded it with an annoying amount of parsing overhead. I was just trying to run my programming program by writing an entire object in a binary file, la

 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(); } 

etc. But I found out that this does not work, because complex data types (for example, string, vector) contain pointers whose values ​​cannot be stored in this way.

So these are the parameters

A) Write complex serialization algorithms to convert all complex data types to primitives, and then save them in binary; or

B) Just write everything in an ASCII file according to my original strategy.

The first method seems too complicated, and the second - elegant.

Are there any other options? Is there a standard procedure?

Note. I saw the boost :: serialization library, which also looks very inelegant and strangely cumbersome, that is, I would just write my own serialization methods if it were the right methodology.

+4
source share
4 answers

Not. Use Boost.Serialization or Google Protocol Buffers. And yes, you need to write functions that will put and retrieve your data to / from the serialization container. This was done for reliable solutions that are expected to work.

This way you get version control, compatibility, and portability of your binary files. If you treat your data as a bunch of bytes and write / read everything, you will not be able to read old files when the structure is changed or when the file is written from the assembly with different sizes of filling / bytes / bytes.

It might work for simple things, but it will break so fast that you just won’t regret not doing it from the very beginning.

+3
source

The two strategies you mentioned

A) Write complex serialization algorithms to convert all complex data types to primitives, and then save them in binary; or

B) Just write everything in an ASCII file according to my original strategy.

As usual, this is done. You essentially create your own file format. The most common paradigm is the piece paradox. When you save an object or set of objects, you first write an int representing the size of the object or a "piece" of data. And the next int represents the object. You might want to include version information, as well as take care of supporting the configurations that you save when a user updates their software.

Option A is useful when you make sure the data is very accurate and makes loading / saving problems easier in C ++. For instance. floats saved in this way will be loaded with the same value as saved.

Option B is useful when you want to see what you are saving, and possibly for a person, to somehow change the data. The floats stored here when loading back will not be exactly the same.

Try looking at other file formats for examples. The Midi file format uses the chunks paradigm, and also has a streaming function that uses option A. The Wavefront obj file format is used in a 3D application, for its simplicity, which uses option B. Everything is read in your favorite text editor.

+2
source

If you want to stick with text serialization, you can try simply overriding:

  • std::ostream& operator <<(std::ostream& os, const Type& obj); for serialization and
  • std::istream& operator >>(std::istream& is, Type& obj); for deserialization.

The library already serializes and deserializes primitive types, you do not need access to internal classes or templates to write your own overrides, and C ++ programmers are already familiar with this concept.

For example, the serializer / deserializer for std::vector might look something like this:

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

Please note that this approach has several limitations (as an exercise for the reader). It is your job to evaluate them and decide if this is the right approach.

+1
source

I am not aware of any standard procedure, it depends on the data that you store. What you are describing is the difference between a shallow and deep representation (like a shallow or deep copy). I would suggest planning a simplified serialization mechanism for each class, depending on what is stored. As you noticed, it’s not enough to just write byte memory when you have basic elements that are not adjacent (or even part) to the class instance.

0
source

Source: https://habr.com/ru/post/1400225/


All Articles