Summary:
I have a structure that is read / written to a file.
This structure often changes, and this causes my read() function to become complicated.
I need to find a good way to handle the changes, saving the number of errors. Optimally, the code should facilitate the search for changes between versions.
I thought of a couple of templates, but I donβt know if I went through all the possible options.
As you will see, the code was mostly in C -like, but I am turning it into C++ .
More details
As I said, my structure often changes (in almost every version of the program).
- Some members are deleted, some members are added, some of them become more complex. This is not a simple case when a new member appears in the structure.
So far, changes in the structure have been processed as follows:
- in version_1 , I used the color map table:
struct Obj { int color_index; }; void Read_Obj( File *f, Obj *o ) { f->read( f, &o->color_index ); } void Write_Obj( File *f, Obj *o ) { f->write( f, o->color_index ); }
- in the next version , I changed it to the form [r, g, b]
struct Obj { int color_r; int color_g; int color_b; }; void Read_Obj( File *f, Obj *o ) { if( f->version() == File::Version1 ) { int color_index; f->read( f, &color_index ); ColorIndex_to_RGB( o, color_index ); // we used color maps back then } else { f->read( f, &o->color_r ); f->read( f, &o->color_g ); f->read( f, &o->color_b ); } } void Write_Obj( File *f, Obj *o ) { f->write( f, o->color_r ); f->write( f, o->color_g ); f->write( f, o->color_b ); }
[short note]
Note that I know I could use
void Read_Obj( File *f, Obj *o ) { if( f->version() == File::Version1 ) { Read_Obj_V1( f, o ); } else { Read_Obj_V2( f, o ); } }
but this leads to duplication of code between each of the sub-functions, since in real life only 1-2 of ~ 20 structure members change in each version. Thus, the remaining 18 lines remain unchanged.
Of course, I can change this policy, if for good reason
[end of brief note]
Now these structures have become complex, and I need to convert them to a class and work in a more object-oriented way.
I saw a template in which you use one class to read for each old version, and then convert the data to a new class.
class Obj_v1 { int m_color_index; read( File *f ) { f->read( f, &m_color_index ); } void convert_to( Obj * ) { } }; class Obj { int m_r; int m_g; int m_b; read( File *f ) { f->read( f, &m_r ); f->read( f, &m_g ); f->read( f, &m_b ); } }; void Read_Obj( File *f, Obj *o ) { if( f.version() == File::Version1 ) { Obj_v1 old(); old.read( f ); old.convert_to( o ); } else { o.read( f ); } } void Write_Obj( File *f, Obj *o ) { o->write( f ); }
However, there are two strategies to solve the problem:
Strategy 1 : direct conversions
void Read_Obj( File *f, Obj *o ) { if( f->version() == File::Version1 ) { Obj_v1 old(); old.read( f ); old.convert_to( o ); } else if( f->version() == File::Version2 ) { Obj_v2 old(); old.read( f ); old.convert_to( o ); } else { o.read( f ); } }
Inconvenience:
- This means that you must update the
convert_to() all Obj_vX classes every time you change the Obj class. Too many opportunities for errors that occur at any given time.
Allowance:
- You can always put the old concept (structure) into a new one - compare with a cascading strategy (hereinafter), where some information may be lost along the way, therefore it cannot be used.
Strategy 2 : cascading transformations
void Read_Obj( File *f, Obj *o ) { Obj_v1 o1(); Obj_v2 o2(); if( f->version() == File::Version1 ) { o1.read( f ); o1.convert_to( o2 ); o2.convert_to( o ); } else if( f->version() == File::Version2 ) { o2.read( f ); o2.convert_to( o ); } else { o.read( f ); } }
Disadvantages:
In v1, there may be some information that was useless in v3, but v5 could use it; however, cascading transformations destroyed this data.
Older versions tend to take longer to create objects.
Allowance:
- You only need to write one
convert_to() each time you change the Obj class. However, a single error in one of the converters in a row can have more serious consequences and can lead to a failure of database consistency. However, you are more likely to find such an error.
Anxiety:
- Could it be that conversion-after-conversion is getting too much noise in objects of older versions that they are wrong?
Question:
Are there any other templates that do this better?
Those of you who have had some experience with my suggestions, what do you think of my concern about the above implementations?
What are the preferred solutions?
Thank you very much