I need to maintain a project that supports running on Linux and Windows. Some codes using preprocessor directives like this are fine.
#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers
#include <windows.h>
#else
#include <unistd.h>
#endif
But some are the actual implementation, which I would like to prevent using preprocessor directives.
void Foo()
{
#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers
code for windows
#else
code for Linux
#endif
some common code...
#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers
code for windows again
#else
code for Linux again
#endif
}
So things get convoluted and harder to maintain. Is there any better way?
The traditional way is to "hide" all the code that is specific to any OS in wrapper functions - you can either do that in complete functions that do a higher level functionality - e.g. have a function that returns all directory entries based on a given path as input, or implement the individual base-functions, e.g. start_read_directory(path)
, read_dir_entry()
, end_read_directory()
- that's just an example functionality, the same principle(s) can be applied on almost any system specific functionality. Wrap it enough, and you wouldn't be able to tell what you are programming for.
In essence, you are doing it wrong if you have a lot of #ifdef in the code itself.
Handle the OS specifics from the build system, not the code. For instance, have two versions of Foo.cpp: one that gets compiled on Linux and another on Windows. Ideally, the header file will be common and all function signatures identical.
You can use a simplified version of the factory pattern.
Have a common interface
class MyClass
{
public:
virtual void Foo() = 0;
};
And for each platform you create a specific class
#import <windows.h>
class MyClassWindows : MyClass
{
public:
virtual void Foo() { /* Do something */ }
};
#import <linux.h>
class MyClassLinux : MyClass
{
public:
virtual void Foo() { /* Do something */ }
};
Then when you need this class, you use your factory:
class MyClassFactory
{
public:
static MyClass* create()
{
#if defined _WIN32
return new MyClassWindows();
#elif defined _LINUX
return new MyClassLinux();
#endif
}
}
There a many variants of this methods, including defining the MyClassFactory::create method in the .cpp of each platform-specific class and only compiling the .cpp for the appropriate platform. This avoids all preprocessing directives, the switching is made by choosing the correct implementation file.
A common pattern would be to provide system independent header files, and platform-specific implementation files.
Nothing platform specific in the header:
class Foo
{
...
};
In two different implementation files, foo_linux.cpp
Foo::Foo() { .. linux code }
foo_windows.cpp
Foo::Foo() { .. windows code }
and maybe platform independent implementation in foo.cpp
void Foo::plat_independent_function()
Your platform builds then link in foo.cpp and foo_platform.cpp
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With