最近我想在我的框架中重构一些依赖于平台的低级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。)UnionOfPlatforms<B, A, C>
等辅助标记类型与#if Platform_A || Platform_B || Platform_C
实际等效来实现,以模仿#if Platform_B || Platform_A || Platform_C
和Platform_B
之间的等效性。在实现方面,这是一个重要问题,因为C ++重载分辨率(特别是完全匹配)规则基于标称类型。如果我偶尔搞砸了订单,很可能很难测试和调试。 (如果Platform_C
和struct 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 ......)
答案 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上工作
关于设计:
我在这里要做的是将问题分为API部分和实现部分。 API将处理部分操作系统差异,实现部分将完成剩下的工作。以及如何决定哪个做什么? 这来自良好的API实践。
例如,OS版本依赖性将转到实现部分。 还可以组合一些非常相似的OS:es。如果你走到极端, 您可以创建两个OS适配器:general和windows。 : - )
关于虚拟会员:
通常,模板编程和虚拟成员被视为彼此的替代。你知道,快速准确的代码,自动类型检查与多态性的能力。
在这里,我看不出在同一解决方案中包含这两种模式会带来什么好处。它让人困惑。但那是 - 当然 - 只是我的意见。