基于标签调度的专业化,用于在C ++代码中进行平台识别和仿真

时间:2015-11-22 07:27:45

标签: c++ c++11 cross-platform

背景

最近我想在我的框架中重构一些依赖于平台的低级API,用C ++ 11编写。当前API通过条件包含支持不同的平台,在编译时选择所有可能的实现之一。但是,在某些情况下,API应该支持更多选项,即模拟其他平台。然后我可以将当​​前的API重定向到这些新添加的仿真API。

由于平台集仍然完全在编译时确定,我可以使用模板特化等技术来保持运行时开销。但是,潜在的代码库很大。对每个API(特别是自由函数)使用类模板特化会使代码膨胀很多。函数模板(如果没有包装器直接使用)很容易搞砸了重载,也很难弄清楚如何指定匹配的顺序。因此,我认为使用标签调度可能会更好。

为了说明这个想法,这里有一个例子:

目前:

#ifdef _WIN32
#   define Platform_Win32 1
#endif

inline char
GetNativePathSeparator()
{
#if Platform_Win32
    rerturn '\\';
#else
    return '/';
#endif
}

将来:

#define Platform_Win32_ID 0x0001 // some hard-coded magic number here...

#ifdef _WIN32
#define NativePlatform Platform_Win32_ID
#endif

template<std::uintmax_t ID>
using PlatformID = std::integral_constant<std::uintmax_t, ID>;

// For "all" platforms, as the default fallback.
struct BaseTag
{};

struct Win32Tag : BaseTag, PlatformID<Platform_Win32_ID>
{};

struct NativePlatformTag : BaseTag, PlatformID<NativePlatform>
{};

// The new emluation APIs. These APIs should be available for all host platforms, even the actual implementation used depends on the emulated target platform.

inline char
GetPlatformPathSeparator(Win32Tag)
{
    rerturn '\\';
}

inline char
GetPlatformPathSeparator(BaseTag)
{
    rerturn '/';
}

// Hmm... the implementation is even "portable"!
inline char
GetNativePathSeparator()
{
    return GetPlatformPathSeparator(NativePlatformTag());
}

// Note there would be many APIs like this.
// Macros can be used here to simplify the code for forwarding calls and make it easy enough to maintain, but it is relative complex for template specialization approach.

但事情并非总是那么简单。平台中立本身已经是一个怪物......

我可能需要:

struct POSIXTag : BaseTag // POSIX is based on ISO C, which is nearly the "base" of all C++ implementations, no need to dig deeper...
{};

struct UNIXTag : POSIXTag // SUS is based on POSIX.
{};

struct OSXTag : UNIXTag // Now OS X is a UNIX.
{};

还好......但是如果事情不太自信怎么办?

struct GNUTag : POSIXTag // (perhaps not quite true)
{};

struct LinuxTag : GNUTag // (mainstream userland only?)
{};

struct AndroidTag : LinuxTag // (mostly true using C/C++?)
{};

struct BSDTag : POSIXTag // (somewhat questionable...)
{};

struct OSXTag : BSDTag // (ditto)
{};

// #ifdef __CYGWIN__ ??
// #if defined(_NEWLIB_VERSION) && defined(__SCLE) ??

甚至(见http://pubs.opengroup.org/onlinepubs/009695399/help/codes.html):

namespace POSIXTags
{

struct XSI : POSIXTag
{};

struct XSR : XSI
{};

struct SHM : POSIXTag
{};

struct AIO : POSIXTag
{};

struct BAR : POSIXTag
{};

struct CPT : POSIXTag
{};

struct CS : POSIXTag
{};

//...

}

或者更糟:

//...
struct POSIX2013Tag : POSIXTag // XXX: POSIX2008Tag? What about depercation and removal of APIs?
{};

#if _POSIX_SOURCE >= 200809
using POSIXTag = POSIX2013Tag;
#else
//...
#endif

叹息......那已经足够了......(条件包含在各地已经是NG了。)

我的问题

有没有现有技术?

更具体地说,我对以下方面的细节感兴趣:

基本标记类的 base-specifier 中是否应该有virtual

  • 如果是这样,主流实现(G ++ / Clang ++ / MSVC ++等的最近/主干版本)是否可以像简单的std::iterator_tag案例那样优化运行时开销?

  • 如果没有,是否有任何其他解决方案可以解决这里的背景问题,并且(更多)优雅地解决?

请注意,虚拟基础方法有两个主要好处:

  • 明确保持基数不会偶尔重复。这与std::iterator_tag这样的简单案例明显不同,UnionOfPlatforms<A, B, C>的层次结构设计在很早的时候是稳定的/几乎是固定的,并且不太可能在以后(经常)延长。 (虽然存在proposed new iterator category design。)
  • 允许用户不知道模板参数的顺序以及关于所有这里介绍的不同标识符之间的功能集/平台重叠的精确知识。如果没有使用这些特定于平台的界面,大多数用户甚至不需要知道所有支持平台的存在是很自然的。它还简化了这些特定于平台的API的维护工作。这可以通过提供UnionOfPlatforms<B, A, C>等辅助标记类型与#if Platform_A || Platform_B || Platform_C实际等效来实现,以模仿#if Platform_B || Platform_A || Platform_CPlatform_B之间的等效性。在实现方面,这是一个重要问题,因为C ++重载分辨率(特别是完全匹配)规则基于标称类型。如果我偶尔搞砸了订单,很可能很难测试和调试。 (如果Platform_Cstruct Platform_Win32 /*: virtual Platform_WinNT */ {}; struct Platform_Win64 : /*virtual*/ Platform_Win32 {}; struct Platform_MinGW32 : /*virtual*/ Platform_Win32 {}; struct Platform_MinGW64 : /*virtual*/ Platform_MinGW32 {}; 有一些共同的特征,或者哪一个是另一个的子集,怎么办呢?为了实现重载工作,不允许基于模糊性。)注意这个问题的主要部分本质上是(编译时)元sum types的简化实例,C ++类型系统通常不能很好地处理。

这是一个更具体的现实世界&#34;第二点的例子:

std::integral_constant

(此案例已经简化。这里我不会使用Failed to load resource: http://localhost/mapbender3/web/app.php/owsproxy/?url=http%3A%2F%2Flocalhost%2Fmapbender3%2Fweb%2Fapp.php%2Fapplication%2Fmopwmaps%2Finstance%2F9%2Ftunnel&TRANSPARENT=TRUE&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&LAYERS=cite%3ARoad_Master&SRS=EPSG%3A4326&BBOX=62.69026197435,31.478060912429,70.30973802565,35.521939087571&WIDTH=2400&HEIGHT=1273 the server responded with a status of 500 (Internal Server Error) 内容将宏值和标签链接在一起,并假设所有与Win32兼容的平台都基于WinNT,没有Win16 / Win9x / WinCE,我还没有考虑过我应该支持WinRT,甚至是Wine ......)

2 个答案:

答案 0 :(得分:1)

会员可以多次继承。只要找到的所有声明都相同,在多个基础中找到的成员名称仍然可用。确实需要有一个显式范围运算符来进行名称查找;这个解决方案不能纯粹作为隐式转换。

struct foo_bar_tag {
    typedef foo_bar_tag foo_tag;
};

struct a_tag : foo_bar_tag {};
struct b_tag : foo_bar_tag {};
struct d_tag : a_tag, b_tag {};

template< typename has_foo_tag >
typename has_foo_tag::foo_tag
slice_foo_tag( has_foo_tag ) { return {}; }

void f( foo_bar_tag );
void f( foo_baz_tag );

template< typename full_tag >
void g( full_tag t ) {
    f( t ); // error, ambiguous base conversion
    f( slice_foo_tag( t ) ); // OK
    f( typename full_tag::foo_tag{} ); // OK
}

如果标签基类型是模板化的,则可以免费获取成员typedef(作为inject-class-name)。

template< typename >
struct foo_tag {};

struct a_tag : foo_tag< bar > {};
struct b_tag : foo_tag< bar > {};
struct d_tag : a_tag, b_tag {};

void f( foo_tag< bar > );
void f( foo_tag< baz > );

// Rest of example is the same.
// foo_tag< bar >::foo_tag is a typedef to foo_tag< bar >.

答案 1 :(得分:0)

供参考:

您可能想看看他们如何完成操作系统适配层的GTK(或者您已经拥有)。这可能是一个很好的参考。它 宣传在Unix / Linux,Windows和OS / X上工作

http://www.gtk.org/download/

关于设计:

我在这里要做的是将问题分为API部分和实现部分。 API将处理部分操作系统差异,实现部分将完成剩下的工作。以及如何决定哪个做什么? 这来自良好的API实践。

例如,OS版本依赖性将转到实现部分。 还可以组合一些非常相似的OS:es。如果你走到极端, 您可以创建两个OS适配器:general和windows。 : - )

关于虚拟会员:

通常,模板编程和虚拟成员被视为彼此的替代。你知道,快速准确的代码,自动类型检查与多态性的能力。

在这里,我看不出在同一解决方案中包含这两种模式会带来什么好处。它让人困惑。但那是 - 当然 - 只是我的意见。