I'm trying to write an abstraction layer to let my code run on different platforms. Let me give an example for two classes that I ultimately want to use in the high level code:
class Thread
{
public:
Thread();
virtual ~Thread();
void start();
void stop();
virtual void callback() = 0;
};
class Display
{
public:
static void drawText(const char* text);
};
My trouble is: What design pattern can I use to let low-level code fill in the implementation? Here are my thoughs and why I don't think they are a good solution:
In theory there's no problem in having the above definition sit in highLevel/thread.h
and the platform specific implementation sit in lowLevel/platformA/thread.cpp
. This is a low-overhead solution that is resolved at link-time. The only problem is that the low level implementation can't add any member variables or member functions to it. This makes certain things impossible to implement.
A way out would be to add this to the definition (basically the Pimpl-Idiom):
class Thread
{
// ...
private:
void* impl_data;
}
Now the low level code can have it's own struct or objects stored in the void pointer. The trouble here is that its ugly to read and painful to program.
I could make class Thread
pure virtual and implement the low level functionality by inheriting from it. The high level code could access the low level implementation by calling a factory function like this:
// thread.h, below the pure virtual class definition
extern "C" void* makeNewThread();
// in lowlevel/platformA/thread.h
class ThreadImpl: public Thread
{ ... };
// in lowLevel/platformA/thread.cpp
extern "C" void* makeNewThread() { return new ThreadImpl(); }
This would be tidy enough but it fails for static classes. My abstraction layer will be used for hardware and IO things and I would really like to be able to have Display::drawText(...)
instead of carrying around pointers to a single Display
class.
Another option is to use only C-style functions that can be resolved at link time like this extern "C" handle_t createThread()
. This is easy and great for accessing low level hardware that is there only once (like a display). But for anything that can be there multiple times (locks, threads, memory management) I have to carry around handles in my high level code which is ugly or have a high level wrapper class that hides the handles. Either way I have the overhead of having to associate the handles with the respective functionality on both the high level and the low level side.
My last thought is a hybrid structure. Pure C-style extern "C"
functions for low level stuff that is there only once. Factory functions (see 3.) for stuff that can be there multiple times. But I fear that something hybrid will lead to inconsistent, unreadable code.
I'd be very grateful for hints to design patterns that fit my requirements.
You seem to want value semantics for your Thread
class and wonder where to add the indirection to make it portable. So you use the pimpl idiom, and some conditional compilation.
Depending on where you want the complexity of your build tool to be, and if you want to keep all the low level code as self contained as possible, You do the following:
In you high level header Thread.hpp
, you define:
class Thread
{
class Impl:
Impl *pimpl; // or better yet, some smart pointer
public:
Thread ();
~Thread();
// Other stuff;
};
Than, in your thread sources directory, you define files along this fashion:
Thread_PlatformA.cpp
#ifdef PLATFORM_A
#include <Thread.hpp>
Thread::Thread()
{
// Platform A specific code goes here, initialize the pimpl;
}
Thread::~Thread()
{
// Platform A specific code goes here, release the pimpl;
}
#endif
Building Thread.o
becomes a simple matter of taking all Thread_*.cpp
files in the Thread directory, and having your build system come up with the correct -D
option to the compiler.