@Koaftder
There's nothing "specific" in the STL or anything for data serialization, unless you count using the C++ stream classes and implementing the appropriate operator<<() and operator>>() for your classes. Said streams are generally character based, however.
What I did was to create classes something like this (simplifed here as the full system was a complete binary safe OOP storage engine)
class OutputStream {
private:
// write the current architectures' signature
// into the file header
bool writeHeader();
public:
bool open(const char* fileName);
void close();
size_t write8(const void* src, size_t num);
size_t write16(const void* src, size_t num);
size_t write32(const void* src, size_t num);
size_t write64(const void* src, size_t num);
//...
};
class InputStream {
private:
bool swapEndian;
// read the architecture signature
// from the file header
bool readHeader();
// normal readers
size_t read8Norm(void* dst, size_t num);
size_t read16Norm(void* dst, size_t num);
size_t read32Norm(void* dst, size_t num);
size_t read64Norm(void* dst, size_t num);
// readers with endian conversion
size_t read16Swap(void* dst, size_t num);
size_t read32Swap(void* dst, size_t num);
size_t read64Swap(void* dst, size_t num);
public:
bool open(const char* fileName);
void close();
size_t read8(void* dst, size_t num);
size_t read16(void* dst, size_t num);
size_t read32(void* dst, size_t num);
size_t read64(void* dst, size_t num);
//...
};
// typical implementation
inline size_t InputStream::read32(void* dst, size_t num)
{
return swapEndian ? read32Swap(dst, num) : read32Norm(dst, num);
}
class StreamStorable {
public:
virtual bool write(OutputStream& s) = 0;
virtual bool read(InpputStream& s) = 0;
// ensure a proper destructor is called if we
// ever delete a StreamStorable*
virtual ~StreamStorable() {}
}
The idea here is that the OutputStream has methods for writing 8/16/32/64 bit data. It always writes a header to the file first, consisting only of a few bytes of data that include information about the current machines architecture, the type of file data and so on.
The InputStream class, upon opening such a file, checks to see if the written signature is endian compatible with the current machine or not and sets the swapEndian flag accordingly, which in turn dictates which multibyte IO routines are used for reading.
Last but not least, objects that needed to be saved to / read from said streams would implement the StreamStorable interface, thus:
class Something : public StreamStorable {
private:
sint32 foo[2];
sint16 x;
public:
bool write(OutputStream& s) {
return (s.write32(foo,2)==2 && s.write16(&x,1)==1)
}
bool read(InputStream& s) {
return (s.read32(foo,2)==2 && s.read16(&x,1)==1)
}
};
//....
Something blah();
OutputStream out("ram:foo.dat");
blah.write(out);
out.close();
This is actually a total oversimplification of what was really done since the actual Storable interface would write a header of its own and store a small amount of other metadata, including the total size, inclusive of anything dynamically allocated, so that the read functions could completely reconstruct an object that migh have dynamically allocated data in them. However, I hope it gets the general idea across.