Ensuring fields are only added to the end of a struct?
I have a rather odd problem: I need to maintain a chunk of code which involves structs. These structs need to be modified by adding fields to them from time to time. Adding to the end of a struct is what you are supposed to do, and any time you add a field to the middle of a struct, you've broken the code in a non-trivial way.
It's become a nightmare to maintain since these do need to be modified occasionally, and the only way to get out of the business of modifying these structs (and having them break due to the layout change of the previous fields) is to completely rewrite a huge swath of code, which is simply not possible.
What I need to do is find a way to make changing the layout of the struct a compile error. Now, I can solve the problem this way:
struct Foo
{
int *Bar;
int Baz;
};
#ifdef _x86_
static_assert(_offsetof(Foo, Bar) == 0);
static_assert(_offsetof(Foo, Baz) == 4);
#else
static_assert(_offsetof(Foo, Bar) == 0);
static_assert(_offsetof(Foo, Baz) == 8);
#endif
However, this is a huge amount of work because there are roughly 20 of these structs with 4-10 fields each. I can probably minimize this by only asserting that the last field's offset is at the correct loca开发者_JAVA百科tion, but this is still a lot of manual work.
Does anyone have any suggestions for a compiler trick, C++ template metaprogram trick, or otherwise, that would make this easier?
you should probably use a form of struct inheritence (c -style) to make this a little easier for everyone to figure out
basically you would have the struct you dont want modified
//DO not modify this structure!!!
struct Foo
{
int *Bar;
int Baz;
};
and a FooExtensions (or whatever) struct that people can modify willy-nilly
struct FooExtensions
{
struct Foo base;
//go crazy but keep base as the first thing.
};
then to pass a FooExtnesions object to a method expecting a Foo just cast it to Foo, the member alignments will work out. this is a more common pattern for maintaining this kind of backwards compatibility. Obviously people can still ignore the convention but this makes it a little easier to succeed.
you can still add all the static_asserts to make sure that Foo isn't modified
If a comment explaining the situation in your code is not going to do it, neither will asserts, or anything else in your code. This is what will happen:
Programmer adds field to middle of struct
Programmer gets compile error from asserts (or other clever method)
Programmer changes the asserts (or other clever method) so it compiles with the addition in the middle
:-)
My favorite is when people 'improve' carefully engineered objects by just making private members public and writing to them willy-nilly.
A better idea implement rules in source control that prevent turnin of mal-changed code. You'd have to write a program that runs on turnin and saves the structs, then compares them the next time to see how they changed, then have source control run it an you can tell it if its valid. Maybe like fxcop would do for .net (would it?)
much easier - just require the sources with the structs in them, to have you review them before checkin (take away their permission to check them in w/o your approval; depends on your source control).
Add a comment
// New field must be added at the end of the struct
I think you are on the right track with using assertions to generate compile-time errors if fields are added in the wrong place. However, I would recommend writing a script to generate the assert code for you (to avoid all the manual work).
The other solution is to add comments indicating the proper way to add to the struct. When someone modifies in incorrectly, use your source control system to identify the guilty developer. Join the rest of your team in publicly ridiculing the developer. Make them wear an oversized, goofy-looking hat for the rest of the day. Go out to lunch and invite everyone but them. Most likely, they will only make that mistake once.
Honestly, this doesn't seem like a job for the compiler or preprocessor.
It seems like a job for source control scripts. Checking in a new version with a field added mid-struct should simply fail with an appropriate error message.
Aside from that, you could use unit tests. zero the struct, set the value of what you expect to be the last field to be non-zero one way (either using the expected offset or struct.fieldname) and test it the other way.
A technique that seems to work well is numbering the elements of the struct like the following,
struct Foo
{
int *Bar; // Element 1
int Baz; // Element 2
long Bab; // Element 3
char Ban; // Element 4
int Bag; // Element 5
// ADD NEW ELEMENTS HERE
};
It makes it seem more tedious to add to the middle and have to fix the comments. It should also be clear that order is important.
I would look to see if there is a tool or script to generate the asserts for the code.
精彩评论