Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

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:

//include/window.hpp
class Window
{
    //Public interface
}

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

//src/{platform}/window.hpp
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:

//src/window.cpp
//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?

like image 422
CRefice Avatar asked Aug 04 '16 19:08

CRefice


People also ask

Why inheritance is compile time mechanism?

inheritance is always achieched at compile time. the code acquires reusability with extends keyword even before entering into jvm for verification and thus converting to bytecode,although we can only use its features at run-time after the creation of the object.

Can a class inherit from itself?

The class doesn't inherit itself. Every instatiation of Model<N> is a different, unrelated class.


2 Answers

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 {
public:
    void doSomething() {
        static_cast<WindowType *>(this)->doSomethingElse();
    }
};

Then you could do

class WinWindow : public WindowBase<WinWindow> {
public:
    void doSomethingElse() {
        // code
    }
};

And to use it (assuming C++ 14 support):

auto createWindow() {
#ifdef WINDOWS
    return WinWindow{};
#elif UNIX
    return X11Window{};
#endif
}

With C++ 11 only:

auto createWindow()
    ->
#ifdef WINDOWS
    WinWindow
#elif defined UNIX
    X11Window
#endif
{
#ifdef WINDOWS
    return WinWindow{};
#elif defined UNIX
    return X11Window{};
#endif
}

I recommend using auto when you use it, or using it in combination with a typedef:

auto window = createWindow();
window.doSomething();

Object Oriented Style

You could make your Window class be an abstract class:

class Window {
protected:
    void doSomething();
public:
    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 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.

Additionally, this requires the Window to be declared on the heap whereas CRTP doesn't; this might be a problem depending on the use case, but in general, it doesn't matter that much.


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.

like image 195
Justin Avatar answered Oct 13 '22 05:10

Justin


You can use the CRTP pattern to implement static polymorphism:

class WindowBase {
     virtual void doSomething() = 0;
};

template<class WindowType>
class Window : public WindowBase {

    // Static cast when accessing the actual implementation:
    void doSomething() {
         static_cast<WindowType*>(this)->doSomethingElse();
    }
};

class X11WindowImpl : public Window<X11WindowImpl> {
    void doSomethingElse() {
       // blah ...
    }
};

class Win32WindowImpl : public Window<Win32WindowImpl> {
    void doSomethingElse() {
       // blah ...
    }
};

Since your code will be compiled to satisfy a particular target, this should be the leanest option.

like image 8
πάντα ῥεῖ Avatar answered Oct 13 '22 04:10

πάντα ῥεῖ