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

CRe*_*ice 9 c++

在我创建跨平台GUI框架的过程中,我遇到了以下问题:假设我在项目的一般平台无关包含文件夹中有一个中心"Window"类:

//include/window.hpp
class Window
{
    //Public interface
}
Run Code Online (Sandbox Code Playgroud)

然后我有几个依赖于平台的实现类,如下所示:

//src/{platform}/window.hpp
class WinWindow {...}; //Windows
class OSXWindow {...}; //OSX
class X11Window {...}; //Unix
Run Code Online (Sandbox Code Playgroud)

最后,还有原始的Window类'.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
Run Code Online (Sandbox Code Playgroud)

我知道这绝不是有效的C++,这就是重点.我想到了解决这个问题的两种可能的方法,我两者都有问题.

pImpl风格的实现

Make Window保存指向实现类的void指针,并将其分配给每个平台的不同窗口类.但是,每次我想要执行平台相关操作时,我都必须向上转换指针,更不用说在任何地方都包含平台相关文件.

预处理程序指令

class Window :
#ifdef WIN32
private WinWindow
#else ifdef X11
private X11Window //etc.
Run Code Online (Sandbox Code Playgroud)

然而,这听起来更像是一个黑客而不是问题的实际解决方案.

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

Jus*_*tin 10

使用typedef隐藏预处理器

你可以简单地输入相应的窗口类型:

#ifdef WINDOWS
    typedef WinWindow WindowType;
#elif defined // etc
Run Code Online (Sandbox Code Playgroud)

然后你的窗口类可能是:

class Window : private WindowType {
};
Run Code Online (Sandbox Code Playgroud)

不过,这不是一个非常强大的解决方案.最好以面向对象的方式思考,但C++中的OO编程需要运行时成本,除非你使用

奇怪地重复模板模式

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

template<class WindowType>
class WindowBase {
public:
    void doSomething() {
        static_cast<WindowType *>(this)->doSomethingElse();
    }
};
Run Code Online (Sandbox Code Playgroud)

然后你可以做到

class WinWindow : public WindowBase<WinWindow> {
public:
    void doSomethingElse() {
        // code
    }
};
Run Code Online (Sandbox Code Playgroud)

并使用它(假设C++ 14支持):

auto createWindow() {
#ifdef WINDOWS
    return WinWindow{};
#elif UNIX
    return X11Window{};
#endif
}
Run Code Online (Sandbox Code Playgroud)

仅限C++ 11:

auto createWindow()
    ->
#ifdef WINDOWS
    WinWindow
#elif defined UNIX
    X11Window
#endif
{
#ifdef WINDOWS
    return WinWindow{};
#elif defined UNIX
    return X11Window{};
#endif
}
Run Code Online (Sandbox Code Playgroud)

我建议auto您在使用它时使用它,或者将它与typedef结合使用:

auto window = createWindow();
window.doSomething();
Run Code Online (Sandbox Code Playgroud)

面向对象的风格

你可以让你的Window类成为一个抽象类:

class Window {
protected:
    void doSomething();
public:
    virtual void doSomethingElse() = 0;
};
Run Code Online (Sandbox Code Playgroud)

然后将依赖于平台的类定义为子类Window.那么你所要做的就是在一个地方拥有预处理器指令:

std::unique_ptr<Window> createWindow() {
#ifdef WINDOWS
    return new WinWindow;
#elif defined OSX
    return new OSXWindow;
// etc
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会通过调用虚函数来产生运行时成本.CRTP版本在编译时而不是在运行时解析对"虚函数"的调用.

另外,这需要在Window堆上声明,而CRTP不需要; 这可能是一个问题,具体取决于用例,但总的来说,这并不重要.


最终,你必须使用#ifdef某个地方,这样你就可以判断这个平台(或者你可以使用来确定该平台库,但它可能使用#ifdef过),问题只是藏在哪里了.


πάν*_*ῥεῖ 8

您可以使用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();
    }
};
Run Code Online (Sandbox Code Playgroud)
class X11WindowImpl : public Window<X11WindowImpl> {
    void doSomethingElse() {
       // blah ...
    }
};
Run Code Online (Sandbox Code Playgroud)
class Win32WindowImpl : public Window<Win32WindowImpl> {
    void doSomethingElse() {
       // blah ...
    }
};
Run Code Online (Sandbox Code Playgroud)

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