Is it better to use `#ifdef` or inheritance for cross-compiling?
To follow from my previous question about virtual and multiple inheritance (in a cross platform scenario) - after reading some answers, it has occurred to me that I could simplify my model by keeping the server and client classes, and replacing the platform specific classes with #ifdefs (which is what I was going to do originally).
Will using this code be simpler? It'd mean there'd be less files at least! The downside is that it creates a somewhat "ugly" and slightly harder to read Foobar
class since there's #ifdefs
all over the place. Note that our Unix Foobar source code will never be passed to the compiler, so this has the same effect as #ifdef
(since we'd also use #ifdef
to decide what platform specific class to call).
class Foobar {
public:
int someData;
#if WINDOWS
void someWinFunc1();
void someWinFunc2();
#elif UNIX
void someUnixFunc1();
voi开发者_开发百科d someUnixFunc2();
#endif
void crossPlatformFunc();
};
class FoobarClient : public Foobar;
class FoobarServer : public Foobar;
Note: Some stuff (ctor, etc) left out for a simpler example.
Update:
For those who want to read more into the background of this issue, I really suggest skimming over the appropriate mailing list thread. Thing start to get interesting around the 3rd post. Also there is a related code commit with which you can see the real life code in question here.
Preferably, contain the platform dependant nature of the operations within the methods so the class declaration remains the same across platforms. (ie, use #ifdefs in the implementations)
If you can't do this, then your class ought to be two completely separate classes, one for each platform.
My personal preference is to push ifdef
magic into the make files, so the source code stays as clean as possible. Then have an implementation file per platform. This of course implies you can come up with an interface common for all your supported systems.
Edit:
One common way of getting around such a lower denominator design for cross-platform development is an opaque handle idiom. Same idea as ioctl(2)
escape route - have a method returning opaque forward-declared structure defined differently for each platform (preferably in the implementation file) and only use it when common abstraction doesn't hold.
If you're fully sure that you won't use functions from the other OS on the one compiled, then using ifdef's has a lot of advantages:
- Code and variables non used won't be compiled into the executable (however smart-linking helps here a bit)
- It will be ease to see what code is live
- You will be able to easily include platform dependent files.
However, classing based on OS can still have it's benefits:
- You'll be able to be sure that the code compiles on all platforms when doing changes for one
- The code and design will be cleaner
The latter is achieved by ifdefing platform-specific code in the class bodies themselves, or just ifdefing out the non-supported OS instances in compilation.
My preference is to push platform specific issues to the leaf-most modules and try to wrap them into a common interface. Put the specific methods, classes and functions into separate translation units. Let the linker and build process determine which specific translation units to combine. This makes for much cleaner code and easier debugging times.
From experience, I had a project that used #ifdef VERSION2
. I spent a week in debugging because one module used #ifdef VERSION_2
. A subtlety that would be easier to catch if all the version 2 specific code was in version 2 modules.
Having #ifdefs for platform specific code is idiomatic; especially since code for one platform won't even compile if it's enabled on another. Sounds like a good approach to me.
精彩评论