如何在编译时更改 class 继承的内容?
How can I change what a class inherits from at compile-time?
在创建跨平台 GUI 框架的过程中,我遇到了以下障碍:
假设我有一个中央 "Window" class,在项目的通用、平台无关的包含文件夹中:
//include/window.hpp
class Window
{
//Public interface
}
然后我有几个平台相关的实现 classes,像这样:
//src/{platform}/window.hpp
class WinWindow {...}; //Windows
class OSXWindow {...}; //OSX
class X11Window {...}; //Unix
最后还有原来的Windowclass'.cpp文件,这里我要"bind"实现class到一般class .纯粹从概念上讲,这就是我希望能够做到的:
//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
我知道这绝不是有效的 C++,这就是重点。我想到了两种可能的方法来解决这个问题,我都遇到了问题。
pImpl 风格的实现
使 Window 持有一个指向实现 class 的空指针,并将其分配给每个平台的不同 window class。但是,每次我想执行平台相关操作时,我都必须向上转换指针,更不用说到处都包含平台相关文件了。
预处理器指令
class Window :
#ifdef WIN32
private WinWindow
#else ifdef X11
private X11Window //etc.
然而,这听起来更像是黑客而不是问题的实际解决方案。
怎么办?我应该完全改变我的设计吗?我的任何可能的解决方案都含有一点水分吗?
你可以使用CRTP模式来实现静态多态性:
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 ...
}
};
由于您的代码将被编译以满足特定目标,因此这应该是最精简的选项。
没关系。您也可以编写一个 class 并使用 #ifdef 等定义它的内容,但您的解决方案不是 hack。如果您别无选择,这就是编写多平台代码的正确方法。
使用 typedef 隐藏预处理器
您可以简单地 typedef 适当的 window 类型来代替:
#ifdef WINDOWS
typedef WinWindow WindowType;
#elif defined // etc
那么你的 window class 可能是:
class Window : private WindowType {
};
不过,这不是一个非常可靠的解决方案。最好以更面向对象的方式思考,但 C++ 中的 OO 编程会产生运行时成本,除非您使用
奇怪地重复模板模式
您可以使用 curiously repeating template pattern:
template<class WindowType>
class WindowBase {
public:
void doSomething() {
static_cast<WindowType *>(this)->doSomethingElse();
}
};
那你可以
class WinWindow : public WindowBase<WinWindow> {
public:
void doSomethingElse() {
// code
}
};
并使用它(假设 C++ 14 支持):
auto createWindow() {
#ifdef WINDOWS
return WinWindow{};
#elif UNIX
return X11Window{};
#endif
}
仅限 C++ 11:
auto createWindow()
->
#ifdef WINDOWS
WinWindow
#elif defined UNIX
X11Window
#endif
{
#ifdef WINDOWS
return WinWindow{};
#elif defined UNIX
return X11Window{};
#endif
}
我推荐大家在使用的时候使用auto
,或者结合typedef使用:
auto window = createWindow();
window.doSomething();
面向对象的风格
你可以让你的 Window
class 成为一个抽象的 class:
class Window {
protected:
void doSomething();
public:
virtual void doSomethingElse() = 0;
};
然后将依赖于平台的 classes 定义为 Window
的子classes。然后你所要做的就是将预处理器指令放在一个地方:
std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
return new WinWindow;
#elif defined OSX
return new OSXWindow;
// etc
}
不幸的是,这会通过调用虚拟函数产生运行时成本。 CRTP 版本在编译时而不是运行时解析对 "virtual function" 的调用。
此外,这需要在堆上声明 Window
而 CRTP 不需要;这可能是一个问题,具体取决于用例,但总的来说,这并不重要。
最终,你必须在某处使用 #ifdef
,这样你才能确定平台(或者你可以使用确定平台的库,但它可能也使用 #ifdef
),问题是把它藏在哪里。
在创建跨平台 GUI 框架的过程中,我遇到了以下障碍: 假设我有一个中央 "Window" class,在项目的通用、平台无关的包含文件夹中:
//include/window.hpp
class Window
{
//Public interface
}
然后我有几个平台相关的实现 classes,像这样:
//src/{platform}/window.hpp
class WinWindow {...}; //Windows
class OSXWindow {...}; //OSX
class X11Window {...}; //Unix
最后还有原来的Windowclass'.cpp文件,这里我要"bind"实现class到一般class .纯粹从概念上讲,这就是我希望能够做到的:
//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
我知道这绝不是有效的 C++,这就是重点。我想到了两种可能的方法来解决这个问题,我都遇到了问题。
pImpl 风格的实现
使 Window 持有一个指向实现 class 的空指针,并将其分配给每个平台的不同 window class。但是,每次我想执行平台相关操作时,我都必须向上转换指针,更不用说到处都包含平台相关文件了。
预处理器指令
class Window :
#ifdef WIN32
private WinWindow
#else ifdef X11
private X11Window //etc.
然而,这听起来更像是黑客而不是问题的实际解决方案。
怎么办?我应该完全改变我的设计吗?我的任何可能的解决方案都含有一点水分吗?
你可以使用CRTP模式来实现静态多态性:
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 ...
}
};
由于您的代码将被编译以满足特定目标,因此这应该是最精简的选项。
没关系。您也可以编写一个 class 并使用 #ifdef 等定义它的内容,但您的解决方案不是 hack。如果您别无选择,这就是编写多平台代码的正确方法。
使用 typedef 隐藏预处理器
您可以简单地 typedef 适当的 window 类型来代替:
#ifdef WINDOWS
typedef WinWindow WindowType;
#elif defined // etc
那么你的 window class 可能是:
class Window : private WindowType {
};
不过,这不是一个非常可靠的解决方案。最好以更面向对象的方式思考,但 C++ 中的 OO 编程会产生运行时成本,除非您使用
奇怪地重复模板模式
您可以使用 curiously repeating template pattern:
template<class WindowType>
class WindowBase {
public:
void doSomething() {
static_cast<WindowType *>(this)->doSomethingElse();
}
};
那你可以
class WinWindow : public WindowBase<WinWindow> {
public:
void doSomethingElse() {
// code
}
};
并使用它(假设 C++ 14 支持):
auto createWindow() {
#ifdef WINDOWS
return WinWindow{};
#elif UNIX
return X11Window{};
#endif
}
仅限 C++ 11:
auto createWindow()
->
#ifdef WINDOWS
WinWindow
#elif defined UNIX
X11Window
#endif
{
#ifdef WINDOWS
return WinWindow{};
#elif defined UNIX
return X11Window{};
#endif
}
我推荐大家在使用的时候使用auto
,或者结合typedef使用:
auto window = createWindow();
window.doSomething();
面向对象的风格
你可以让你的 Window
class 成为一个抽象的 class:
class Window {
protected:
void doSomething();
public:
virtual void doSomethingElse() = 0;
};
然后将依赖于平台的 classes 定义为 Window
的子classes。然后你所要做的就是将预处理器指令放在一个地方:
std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
return new WinWindow;
#elif defined OSX
return new OSXWindow;
// etc
}
不幸的是,这会通过调用虚拟函数产生运行时成本。 CRTP 版本在编译时而不是运行时解析对 "virtual function" 的调用。
此外,这需要在堆上声明 Window
而 CRTP 不需要;这可能是一个问题,具体取决于用例,但总的来说,这并不重要。
最终,你必须在某处使用 #ifdef
,这样你才能确定平台(或者你可以使用确定平台的库,但它可能也使用 #ifdef
),问题是把它藏在哪里。