Any portable/semi-portable way of forcing class field order in C++?

Started by
5 comments, last by irreversible 8 years, 2 months ago

To be honest, my overall knowledge when it comes to the neurotic moodscape of various compilers is largely lacking. I've pretty much exclusively used VS thus far and barring a few quirks it's been pretty nice to me. Now, as I'm in the process of reorganizing large chunks of my entire codebase, however, I'd like to deal with some potentially less upfront portability issues. Basically, I have a handful of structures whose data fields are read directly from disk in chunks and to bypass serialization I want those to retain their field ordering under all circumstances for a given endianess. So far Googling has led me to the notion that it's, to quote StackOverflow, "a bit of a nightmare".

Is there a remotely portable way to enforce this? What compiler directives should I be looking at? My current target is Windows, but I'm taking considerable care with respect to dependencies to keep my code portable to Linux and OSX at some point. As far endianess goes, I want to maintain code integrity as is, but rebuild source data as needed.

Advertisement
I'm not aware of any compilers reordering data members. The standard does not promise much about member data, but I'm pretty sure it promises sequence. What it very specifically does not promise is padding (that is, how many 'dead bytes' are added between members to make them align nicely on the target platform).

If you have to serialize whole structures (what is in my opinion not a good idea), at least static_assert the size you expect to see. If a compiler tries anything cute regarding padding at least it will quickly and obviously blow up. Unless the compiler adds different padding but ends up at the same total size. In that case, have fun.

It’s really not a nightmare. All members of a structure will be in the order in which they are declared. You only have to handle type sizes and packing issues.

Use uint32_t, int8_t, etc. to ensure the sizes of types.

Use #pragma pack( X ) to help with packing.

Use static_assert() to make compile-time assertions on the size of the structure and potentially the offsets of each structure member.

This will already give you consistent behavior across most compilers and most platforms. If not, either the static_assert() will trip or you will simply find it and fix it for that platform/compiler once.

I support Visual Studio x86 and x64, Xcode iOS, and Xcode Mac OSX, and have no issues or special cases for any single platform.

The only reason people say it is a nightmare is because they assume you will be using every single compiler ever made for every single platform ever.

This is bollocks. You’ll use 3 compilers ever (+2 more if you get to work on consoles), and in these types of issues they are already compatible. It’s nothing.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Just one thing that wasn't mentioned yet: in C++, "public:" / "private:" / "protected:" labels define different blocks of variables. Within these blocks, the order is preserved, however the standard doesn't guarantee that block order will be preserved. I've no idea how often reordering is the case in practice, though simply using structs / making all members of a class public will guarantee that there are no ordering issues.

So far Googling has led me to the notion that it's, to quote StackOverflow, "a bit of a nightmare".

The SO ivory tower is a tad different to the real world.
I've seen this type of code, working easily, in almost every game I've worked on and make extensive use of it on my current projects. Just make it a POD struct full of public (default) members, use fixed-size types, and be aware of padding/alignment rules.
Like LS, I use static assertions on the size to catch stupid errors, and static assertions on the offsets if I'm being really careful.
On all the platforms/compilers that you care about, the padding/alignment rules will almost certainly be predictable, fairly sane, and reliable enough to write this kind of code without even having to worry about portability issues.

Biggest portability issue that I have is sometimes I have a pointer field in these structs -- or an integer 'offset' that is converted into a real pointer on load. This creates two different versions of the struct for 32/64bit platforms, which sometimes is fine if you're building the data per platform (just be sure to use a pointer-sized integer), or alternatively I have a typedef for a "cross-platform pointer sized integer", which is basically uint64_t laugh.png

Third-ing. That is my experience as well.

I wouldn't call their world an "ivory tower" of academia that is not the real world. There are real-world projects where you do need to support all the devices on all the systems. For example, if you're working on the Boost project or other widespread multi-system library.

But that is not games. Games only hit a handful of systems and compilers, as LSpiro covered so nicely. The effort required is minimal. Normally everything works the first time, and the rare times it doesn't, the fix is not difficult.

Much appreciated, guys!

This topic is closed to new replies.

Advertisement