quote:Original post by justinwalsh
There is still no answer as to why doing it the way that was given as an example, serialization, is better then the way i am trying to illistrate.
Serialization is the name of the process of reading/writing objects to a stream. Your own technique is a form of serialisation, but which is only valid for C-style structs (a.k.a. C++ POD-types).
One reason why your solution is bad is that it is very brittle.
If you modify the internal structure of your class, by adding/removing/modifying/rearranging members, your previously serialized data becomes garbage. Furthermore, it is limited with respect to the kind of data that can be serialized. It also assumes that all the data members in the class have to be saved, are all available for read/write access and are *all* that the class contain. i.e. no compiler-generated ancillary data - there''s a reason why constructors are ''special functions'', right ?
In addition to being modifiable via a text editor, keyword-based serialization, as proposed by C-Junkie (even though his code is a first-class abomination), offers more flexibility. If you add new members to your class, you can usually provide sensible default for those members which had not been previously serialized. If your remove member, those entries which have no corresponding data members can be ignored. Though, I''ll admit that kind of machinery is not always appropriate.
Yet, an important benefit of explicit member-by-member serialization is that you can recursively serialize complex objects by calling the serialization functions of the appropriate data members (in C-Junkie''s example, by overloading operator<< and operator>> for the members''s types).
So, take the following example, using formatted I/O.
class Foo{ int x,y,z; friend std::istream& operator>>( std::istream&, Foo& ); friend std::ostream& operator<<( std::ostream&, const Foo& );};class Bar{ Foo a,b; std::string c; int d,e,f; friend std::istream& operator>>( std::istream&, Bar& ); friend std::ostream& operator<<( std::ostream&, const Bar& );}std::ostream& operator<<( std::ostream& os, const Foo& f ){ os << f.x << '' '' << f.y << '' '' << f.z; return os;}std::istream& operator>>( std::istream& is, Foo& f ){ is >> f.x >> f.y >> f.z; return is;}std::ostream& operator<<( std::ostream& os, const Bar& b ){ // We''re not saving b.d; os << b.a << ''\n'' << b.b << ''\n'' << b.c << ''\n'' // Save string on a separate line << b.e << ''\n'' << b.f << std::endl; return os;}std::istream& operator>>( std::istream& is, Bar& b ){ is >> b.a >> b.b; std::getline(is,b.c); // Read string off the whole line is >> b.c >> b.e >> b.f; b.d = 42; // Using a default value; return is;}
Note how Foo objects are automatically written and read back correctly. Of course, if you want unformatted (i.e. binary) I/O, it''s possible too.
class Foo{ int x,y,z;public: std::istream& Load( std::istream& ); std::ostream& Save( std::ostream& ) const;};class Bar{ Foo a,b; std::string c; int d,e,f;public: std::istream& Load( std::istream& ); std::ostream& Save( std::ostream& ) const;}std::ostream& Foo::Save( std::ostream& os ) const{ os.write( (const char*)&x, sizeof(x) ); os.write( (const char*)&y, sizeof(y) ); os.write( (const char*)&z, sizeof(z) ); return os;}std::istream& Foo::Load( std::istream& is ){ is.read( (char*)&x, sizeof(x) ); is.read( (char*)&y, sizeof(y) ); is.read( (char*)&z, sizeof(z) ); return is;}std::ostream& Bar::Save( std::ostream& os ) const{ a.Save(os); b.Save(os); int size = c.size(); os.write( (const char*)&size, sizeof(size) ); os.write( c.data(), size ); // Still not saving d os.write( (char*)&e, sizeof(e) ); os.write( (char*)&f, sizeof(f) ); return os;}std::istream& Bar::Load( std::istream& is ){ a.Load(is); b.Load(is); // Load an arbitrary-length string int size; is.read( (char*)&size, sizeof(size) ); char* buffer = new char[size] is.read( buffer, size ); c.assign( buffer, buffer+size); delete[] buffer; d = 42; is.read( (char*)&e, sizeof(e) ); is.read( (char*)&f, sizeof(f) ); return is;}
Or with a bit of template magic
template<class T> std::ostream& Save( std::ostream& os, const T& t ){ return t.Save(os);}template<>std::ostream& Save<int>( std::ostream& os, const int& t ){ os.write( (const char*)&t, sizeof(t) ); return os;}template<>std::ostream& Save<std::string>( std::ostream& os, const std::string& t ){ int size = c.size(); Save(os, size); os.write( c.data(), size ); return os;}std::istream& Load( std::istream& is, T& t ){ return t.Load(is);}template<>std::istream& Load<int>( std::istream& is, int& t ){ is.read( (char*)&t, sizeof(t) ); return is;}template<>std::istream& Load<std::string>( std::istream& is, std::string& t ){ int size; Load(is, size); char* buffer = new char[size] is.read( buffer, size ); t.assign( buffer, buffer+size); delete[] buffer; return is;}std::ostream& Foo::Save( std::ostream& os ) const{ Save(os, x); Save(os, y); Save(os, z); return os;}std::istream& Foo::Load( std::istream& is ){ Load(is, x); Load(is, y); Load(is, z); return is;}std::ostream& Bar::Save( std::ostream& os ) const{ Save(os,a); Save(os,b); Save(os,c); // Still not saving d Save(os,e); Save(os,f); return os;}std::istream& Bar::Load( std::istream& is ){ Load(is,a); Load(is,b); Load(is,c); d = 42; Load(is,e); Load(is,f); return is;}
There are of course many other ways to implement it, including ways that will actally compile
So, in conclusion, bit blasting is fine for basic types and C structures, but once you get something more complex, saving each member explicitely is the way to go.
[ Start Here ! | How To Ask Smart Questions | Recommended C++ Books | C++ FAQ Lite | Function Ptrs | CppTips Archive ]
[ Header Files | File Format Docs | LNK2001 | C++ STL Doc | STLPort | Free C++ IDE | Boost C++ Lib | MSVC6 Lib Fixes ]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan