使用 CRTP 分离平台特定代码
Using CRTP to separate platform specific code
我最近想到了使用 CRTP(奇怪的重复模板模式)分离不同平台特定实现(可能是 Win32/X、opengl/dx/vulkan 等...):我想到了类似的东西这个:
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
然后我会在 XDisplayDevice.h 中以相同的方式提供另一个实现。
最后,我会在 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
}
}
所以我可以在 main.cpp 中这样调用它:
int main()
{
CDisplayDevice my_device;
while(my_device->run_frame())
{
do_some_magic();
}
}
您认为这是处理平台特定代码的好方法吗?
PS:由于平台限制(android、ps4 等),我避免了食物和多态性,其中指针调用很重要。
我在这里并没有真正看到 CRTP 的好处,代码中仍然有特定于平台(而不是特定于功能)的 ifdef,这往往会使事情更难阅读和维护。我通常更喜欢在不同的源文件中使用不同的实现 - 事实上,通常每个平台都有单独的目录。
如:
- platform/win64
- platform/win32
- platform/gnu-linux
- platform/freebsd
这样你可以在很大程度上避免 ifdef 混乱,并且你通常知道在哪里可以找到平台特定的东西。您还知道为了将内容移植到另一个平台需要编写什么。然后可以使构建系统成为 select 正确的源而不是预处理器。
考虑这段代码:
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
{ ... }
}
};
这将使用特定于子系统的调用,并在公共 API 之间将它们抽象出来,而不涉及虚拟调用。您甚至可以内联 run_frame()
实现。
编辑(地址评论问题):
考虑一下:
#ifdef FLAG_SPECIFYING_OPEN_GL
using Device = DisplayDevice<OpenGLTraits>;
#elif FLAG_SPECIFYING_VULKAN
using Device = DisplayDevice<VulkanTraits>;
...
#endif
客户代码:
Device device;
device.do_your_operation();
我最近想到了使用 CRTP(奇怪的重复模板模式)分离不同平台特定实现(可能是 Win32/X、opengl/dx/vulkan 等...):我想到了类似的东西这个:
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
然后我会在 XDisplayDevice.h 中以相同的方式提供另一个实现。 最后,我会在 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
}
}
所以我可以在 main.cpp 中这样调用它:
int main()
{
CDisplayDevice my_device;
while(my_device->run_frame())
{
do_some_magic();
}
}
您认为这是处理平台特定代码的好方法吗?
PS:由于平台限制(android、ps4 等),我避免了食物和多态性,其中指针调用很重要。
我在这里并没有真正看到 CRTP 的好处,代码中仍然有特定于平台(而不是特定于功能)的 ifdef,这往往会使事情更难阅读和维护。我通常更喜欢在不同的源文件中使用不同的实现 - 事实上,通常每个平台都有单独的目录。
如:
- platform/win64
- platform/win32
- platform/gnu-linux
- platform/freebsd
这样你可以在很大程度上避免 ifdef 混乱,并且你通常知道在哪里可以找到平台特定的东西。您还知道为了将内容移植到另一个平台需要编写什么。然后可以使构建系统成为 select 正确的源而不是预处理器。
考虑这段代码:
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
{ ... }
}
};
这将使用特定于子系统的调用,并在公共 API 之间将它们抽象出来,而不涉及虚拟调用。您甚至可以内联 run_frame()
实现。
编辑(地址评论问题):
考虑一下:
#ifdef FLAG_SPECIFYING_OPEN_GL
using Device = DisplayDevice<OpenGLTraits>;
#elif FLAG_SPECIFYING_VULKAN
using Device = DisplayDevice<VulkanTraits>;
...
#endif
客户代码:
Device device;
device.do_your_operation();