CRefice CRefice - 2 months ago 6x
C++ Question

How can I change what a class inherits from at compile-time?

In my quest to create a cross-platform GUI Framework, I have hit the following snag:
Suppose I have a central "Window" class, in the project's general, platform-independent include folder:

class Window
//Public interface

I then have several platform-dependent implementation classes, like so:

class WinWindow {...}; //Windows
class OSXWindow {...}; //OSX
class X11Window {...}; //Unix

Finally, there is the original Window class' .cpp file, where I want to "bind" the implementation class to the general class. Purely conceptually, this is what I want to be able to do:

//Suppose we're on Windows

#include "include/window.hpp"
#include "src/win/window.hpp"
class Window : private WinWindow; //Redefine Window's inheritance

I know this is by no means valid C++, and that's the point. I have thought of two possible ways to solve this problem, and I have problems with both.

pImpl-style implementation

Make Window hold a void pointer to an implementing class, and assign that to a different window class for each platform. However, I would have to up-cast the pointer every time I want to perform a platform dependent-operation, not to mention include the platform dependent file everywhere.

Preprocessor directives

class Window :
#ifdef WIN32
private WinWindow
#else ifdef X11
private X11Window //etc.

This, however, sounds more like a hack than an actual solution to the problem.

What to do? Should I change my design completely? Do any of my possible solutions hold a little bit of water?


Using typedef to hide the preprocessor

You could simply typedef the appropriate window type instead:

#ifdef WINDOWS
    typedef WinWindow WindowType;
#elif defined // etc

Then your window class could be:

class Window : private WindowType {

This isn't a very robust solution, though. It is better to think in a more Object Oriented way, but OO programming in C++ comes at a runtime cost, unless you use the

Curiously repeating template pattern

You could use the curiously repeating template pattern:

template<class WindowType>
class WindowBase {
    void baseClassFunction() {
        static_cast<WindowType *>(this)->doSomething();

Then you could do

class WinWindow : public WindowBase<WinWindow> {
    void doSomething() {
        // code

And to use it:

template<class WindowType>
WindowBase<WindowType> createWindow() {
#ifdef WINDOWS
    return WinWindow;
#elif defined // etc

Object Oriented Style

You could make your Window class be an abstract class:

class Window {
    void doSomething();
    virtual void doSomethingElse() = 0;

Then define your platform-dependent classes as subclasses of Window. Then all you'd have to do is have the preprocessor directives in one place:

std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
    return new WinWindow;
#elif defined OSX
    return new OSXWindow;
// etc

Unfortunately, this requires that the Windows be placed on the heap, and it incurs a runtime cost through calls to the virtual function. The CRTP version resolves calls to the "virtual function" at compile time instead of at runtime.

Ultimately, you do have to use the #ifdef somewhere, so you can determine the platform (or you could use a library that determines the platform, but it probably uses #ifdef too), the question is just where to hide it.