+3  A: 

void Read_Obj( File *f, Obj *o ) {
if( f->version() == File::Version1 ) {

The if is so to say a hidden switch/case. And switch/case in C++ is generally interchangeable with polymorphism. Example:

struct Reader {
   virtual void Read_Obj( File *f, Obj *o ) = 0;
   /* methods to read further objects */
}

struct ReaderV1 : public Reader {
   void Read_Obj( File *f, Obj *o ) { /* ... */ };
   /* methods to read further objects */
}

struct ReaderV2 : public Reader {
   void Read_Obj( File *f, Obj *o ) { /* ... */ };
   /* methods to read further objects */
}

And then instantiate the appropriate Reader descendant after opening the file and detecting the version number. That way you would have only one file version check in the top level code, instead of polluting all of the low-level code with the checks.

If code is common between the file version, for convenience you can also put it into the base reader class.

I would strongly advise against the variant with class Obj_v1 and class Obj where the read() method belongs to the Obj itself. This way one easily end-up with circular dependencies and also it is a bad idea to make an object aware of its persistent presentation. IME (in my experience) it is better to have the 3rd party reader class hierarchy responsible for that. (As in the std::iostream vs. std::string vs. operator <<: stream doesn't know string, string doesn't know stream, only the opeartor << knows both.)

Otherwise, I personally do not see any big difference between your "Strategy 1" and "Strategy 2". They both use the convert_to() what I personally think is superficial. IME solution with the polymorphism should be used instead - automatically converting everything to the up-to-date version of the object class Obj, without the intermediate class Obj_v1 and class Obj_v2. Since with polymorphism you would have a dedicated read function for every version, ensuring proper object recreation from the read information is easy.

Are there any other patterns that do a better job at this? The ones of you that had some experience with my proposals, what do you think of my worries on the above implementations? Which are preferable solutions?

This is precisely what polymorphism was intended to address and how I generally do such tasks myself.

This is related to object serialization, but I have not seen a single serialization framework (my info is likely outdated) which was capable of supporting several version of the same class.

I personally did end up several times with the following serialization/deserialization class hierarchy:

  • abstract reader interface (very slim by definition)
  • utility classes implementing the reading and writing of the actual objects from/to the streams (fat, highly reusable code, was used for network transfers too)
  • versioned implementations of the reader interface (relatively slim, reuse the fat utility classes)
  • writer interface/class (I was always writing up-to-date version of the file. Versioning was using only during reading.)

Hope that helps.

Dummy00001
This idea about decoupling the object from serialization is really good. I suppose that it will allow me to add different serialisation flavours ( binary, XML, ... ). Thanks!
Grim Fandango
+2  A: 

You may be able to put Google Protocol Buffers to work.

The main idea beyond protobuf is to decorrelate the actual serialization from the class information, because you create a class dedicated to the serialization... but the real benefit lies elsewhere.

The information encoded by protobuf is naturally both backward and forward compatible, so you if you add information and decode an old file: the new information won't be there. On the other hand, if you remove information, it'll skip it during the decoding.

This means that you leave the version handling to protobuf (without any real version number in fact) and then when changing your class:

  • you stop retrieving the information you don't need any longer
  • you add new fields for the new pieces of information you have

It may also help you think better about what to save and in which format, it is okay to transform the data before saving it (encoding) and transform it back when reading (decoding), so the actual format of the save should change less frequently (you would add items, but you should not have to refactor the already encoded data too frequently).

Matthieu M.
See more at http://code.google.com/apis/protocolbuffers/
laust.rud
I suppose what you are describing is something that does not need to change the File Version when new data is added or removed from the struct. Thus, I do not need to change the reader code often to take care of missing or new members. I have already such mechanisms, but they don't help in case I have to add a more complex entity (a new complex struct pointer in my struct).I find the last idea (about writing slightly different data to the file than the actual data of the struct) really interesting. Thank you!
Grim Fandango