Coder32 Coder32 - 3 months ago 20
C++ Question

Using CRTP to seperate platform specific code

I recently got this idea to separate different platform specific implementations (could be Win32/X, opengl/dx/vulkan, etc...) using CRTP (curiously recurring template pattern): I thought of something like this:

IDisplayDevice.h

#pragma once
#include "OSConfig.h"

namespace cbn
{

template <class TDerived> // Win32 type here
struct IDisplayDevice
{
bool run_frame(void) {
return
static_cast<const TDerived*>(this)->run_frame();
}
// a lot of other methods ...
};
}


Win32DisplayDevice.h:

#pragma once
#include "OSConfig.h"
// make sure it only gets compiled on win32/64
#if defined(CBN_OS_WINDOWS)

namespace cbn
{
class CWin32DisplayDevice
: public IDisplayDevice<CWin32DisplayDevice> {
public:
bool run_frame(void) {
call_hInstance();
call_hWnd();
#ifdef CBN_RENDERAPI_DX11
call_dx11_bufferswap();
#endif
return some_state;
}
private:
};
}
#endif


I would then provide an other implementation the same way in XDisplayDevice.h.
Finally, I would make a common interface in DisplayDevice.h:

#include "Win32DisplayDevice.h"
#include "XDisplayDevice.h"

namespace cbn
{
class CDisplayDevice
{
public:
CBN_INLINE
bool run_frame(void) { return device_->run_frame(); }
private:
#if defined(CBN_OS_WINDOWS)
CWin32DisplayDevice device_;
#elif defined(CBN_OS_LINUX)
CXDisplayDevice device_;
#elif // and so on
#else
// does nothing ...
CNillDisplayDevice device_;
#endif
}
}


So I could call it in main.cpp like:

int main()
{
CDisplayDevice my_device;
while(my_device->run_frame())
{
do_some_magic();
}
}


Do you think this would be a good way to deal with platform specific code ?

PS: I avoid victuals and polymorphism because of platform restraints (android, ps4, etc...) where pointer calls matter.

Answer

Consider this code:

struct OpenGLTraits // keep this in it's own files (.h and .cpp)
{
    bool run_frame() { /* open gl specific stuff here */ }
};


struct VulkanTraits // keep this in it's own files (.h and .cpp)
{
    bool run_frame() { /* vulkan specific stuff here */ }
};

template<typename T>
class DisplayDevice
{
    using graphic_traits = T;
    graphic_traits graphics; // maybe inject this in constructor?

    void do_your_operation()
    {
        if(!graphics.run_frame()) // subsystem-specific call
        { ... }
    }
};

This will use subsystem-specific calls, and abstract them away between a common API, without a virtual call involved. You can even inline the run_frame() implementations.

Comments