C++ 如何在编译时更改类继承的内容?

C++ 如何在编译时更改类继承的内容?,c++,C++,在创建跨平台GUI框架的过程中,我遇到了以下障碍: 假设我在项目的常规、独立于平台的include文件夹中有一个中心“Window”类: //include/window.hpp class Window { //Public interface } 然后我有几个依赖于平台的实现类,如下所示: //src/{platform}/window.hpp class WinWindow {...}; //Windows class OSXWindow {...}; //OSX class X

在创建跨平台GUI框架的过程中,我遇到了以下障碍: 假设我在项目的常规、独立于平台的include文件夹中有一个中心“Window”类:

//include/window.hpp
class Window
{
    //Public interface
}
然后我有几个依赖于平台的实现类,如下所示:

//src/{platform}/window.hpp
class WinWindow {...}; //Windows
class OSXWindow {...}; //OSX
class X11Window {...}; //Unix
最后,还有一个原始的Window class“.cpp文件,我想在其中将实现类“绑定”到常规类。纯粹从概念上讲,这就是我希望能够做到的:

//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保留一个指向实现类的空指针,并为每个平台将其分配给不同的Window类。但是,每当我想要执行平台相关的操作时,我都必须向上投射指针,更不用说到处包含平台相关的文件了

预处理器指令

class Window :
#ifdef WIN32
private WinWindow
#else ifdef X11
private X11Window //etc.
然而,这听起来更像是一种黑客行为,而不是问题的实际解决方案

怎么办?我应该完全改变我的设计吗?我的任何可能的解决方案都有一点水分吗?

您可以使用该模式实现静态多态性:

类WindowBase{
虚空doSomething()=0;
};
模板
类窗口:公共WindowBase{
//访问实际实现时的静态强制转换:
无效剂量测定法(){
静态(本)->doSomethingElse();
}
};

类X11WindowImpl:公共窗口{ void doSomethingElse(){ //废话。。。 } };
类Win32WindowImpl:公共窗口{
void doSomethingElse(){
//废话。。。
}
};

由于您的代码将被编译以满足特定的目标,因此这应该是最精简的选项。

没问题。您也可以编写一个类,并使用#ifdef等定义其内容,但您的解决方案不是一个黑客。如果没有其他选择,这是编写多平台代码的正确方法。

使用typedef隐藏预处理器 您只需键入适当的窗口类型即可:

#ifdef WINDOWS
    typedef WinWindow WindowType;
#elif defined // etc
那么您的窗口类可以是:

class Window : private WindowType {
};
不过,这不是一个非常健壮的解决方案。最好用更面向对象的方式思考,但是C++中的面向对象编程要在运行时花费,除非你使用

奇怪的重复模板模式 您可以使用:

< + >仅用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();
面向对象风格 您可以将
窗口
类设置为抽象类:

class Window {
protected:
    void doSomething();
public:
    virtual void doSomethingElse() = 0;
};
然后将平台相关类定义为
Window
的子类。然后,您所要做的就是将预处理器指令放在一个位置:

std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
    return new WinWindow;
#elif defined OSX
    return new OSXWindow;
// etc
}
std::unique_ptr createWindow(){
#ifdef窗口
返回新窗口;
#elif定义的OSX
返回新的OSXWindow;
//等
}
不幸的是,这会通过调用虚拟函数产生运行时成本。CRTP版本在编译时而不是运行时解析对“虚拟函数”的调用

此外,这需要在堆上声明
窗口
,而CRTP不需要;这可能是一个问题,具体取决于用例,但一般来说,这并不重要



最终,你必须在某个地方使用
#ifdef
,这样你就可以确定平台(或者你可以使用一个确定平台的库,但它可能也使用
#ifdef
),问题是在哪里隐藏它。

@VincentSavard OP清楚地解释了他需要什么,并且以正确的方式提出了这个问题,根据SO规则,@ΔλЛ我不确定这与任何事情有什么关系。要么OP自信自己解释得足够清楚,不觉得有必要添加这样的评论,或者他不是,他应该花必要的时间对自己的问题充满信心。似乎
窗口
类有两个概念上不同的角色——一个是定义平台应该实现的接口,另一个是根据平台特定的类来实现,与其说是一个界面,不如说是一个具体的操作对象。将它们分开应该可以解决问题——让接口部分成为由不同平台实现的抽象接口。让具体的部分包含一些对界面的引用,这样您就可以对所有窗口通用的一组定义的操作进行操作。我添加了最后一条语句,以防我出现一些格式错误或错误something@CRefice你不需要包括那句话。你的问题格式很好。如果有人担心格式错误或您的意思,他们会发表评论。预处理器选项是最糟糕的选择之一。在“面向对象风格”中,运行时成本并非来自“指针要求”——CRTP中
baseClassFunction()
的实现也需要使用指针。运行时成本来自于对虚拟函数分派的需求——这在运行时发生——而CRTP在编译时解决了这一问题。@Peter确实如此。我想我也试图说,它必须用“面向对象的样式”来声明堆,除非我理解C++比我想象的要少。无论如何,创建对象是必要的,但是调用非静态成员函数隐式地取消<代码>这个< /C>指针,不管对象是如何创建的。而且对象不会“在堆上声明”。@Peter是的,我的语言不是最好的。我的意思是,我知道如何以这种“面向对象”的方式实现它的唯一方法是使用
new
,它将对象放在堆中并返回指针。能够将对象放置在堆栈上是很有用的,但是需要“使用
新建
”(
class WinWindow : public WindowBase<WinWindow> {
public:
    void doSomethingElse() {
        // code
    }
};
auto createWindow() {
#ifdef WINDOWS
    return WinWindow{};
#elif UNIX
    return X11Window{};
#endif
}
auto createWindow()
    ->
#ifdef WINDOWS
    WinWindow
#elif defined UNIX
    X11Window
#endif
{
#ifdef WINDOWS
    return WinWindow{};
#elif defined UNIX
    return X11Window{};
#endif
}
auto window = createWindow();
window.doSomething();
class Window {
protected:
    void doSomething();
public:
    virtual void doSomethingElse() = 0;
};
std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
    return new WinWindow;
#elif defined OSX
    return new OSXWindow;
// etc
}