0

Pimpl是“实现指针”的缩写,它提供了一种方便的方法来隐藏类中的实现。我正在实现一个 Window 类,它对此类的用户隐藏特定于平台的功能和结构,因此类接口最终看起来很干净:

class Window{
public:
    /// Constructors & destructors:
    Window(void);
    Window(Window const& window_);
    Window(Window&& window_);
    explicit Window(std::string const& title_);
    explicit Window(std::string&& title_);
    ~Window(void);
    /// Member data:
    bool visible = false;
    ContextGraphics graphics_context;
    std::array<unsigned long, 2> position = {{0}}, size = {{800, 600}};
    std::string title;
    /// Member functions:
    void apply_changes(void);
    Window& center_position(void);
    bool enabled(void) const;
    void update(void);
    /// Member functions (overloaded operators, assignment):
    Window& operator=(Window const& window_);
    Window& operator=(Window&& window_);
private:
    /// Inner classes:
    class Helper;
    /// Member data:
    std::unique_ptr<Window::Helper> _m_opHelper;
};

幕后都是那些讨厌的 WINAPI 调用等,当我可能针对更广泛的支持平台时,类头根本不需要更改,只需要更改源文件。非常好用,不用写那么多!

但是,这个对象似乎是我的问题(在类内):

ContextGraphics graphics_context;

这是一个 Direct3D/OpenGL 图形上下文(“ContextGraphics”的用户可以确定),您可能已经猜到,它也使用 pimpl-idiom:

class ContextGraphics{
    /// Friends:
    friend class Window;
public:
    /// Enumerations:
    enum class API : unsigned int{
        API_DEFAULT,
        API_DIRECT3D,
        API_OPENGL
    };
    /// Constructors & destructors:
    ContextGraphics(void);
    explicit ContextGraphics(ContextGraphics::API const& api_);
    explicit ContextGraphics(ContextGraphics::API&& api_);
    ~ContextGraphics(void);
    /// Member data:
    ContextGraphics::API api = ContextGraphics::API::API_DEFAULT;
    unsigned int color_depth : 6; // 2 ^ 6 = 64
    /// Member functions:
    bool api_available(void) const noexcept;
    bool enabled(void) const;
    /// Member functions (overloaded operators, assignment):
    ContextGraphics& operator=(ContextGraphics const& context_graphics_);
    ContextGraphics& operator=(ContextGraphics&& context_graphics_);
private:
    /// Constructors & destructors:
    ContextGraphics(ContextGraphics const& context_graphics_);
    ContextGraphics(ContextGraphics&& context_graphics_);
    /// Inner classes:
    class Helper;
    /// Member data:
    std::unique_ptr<ContextGraphics::Helper> _m_opHelper;
    /// Member functions:
    void create(void);
    void destroy(void);
};

我面临的问题是编译器:

Window.cpp|145|错误:无效使用不完整类型'class ContextGraphics::Helper'|

ContextGraphics.hpp|43|错误:'class ContextGraphics::Helper'的前向声明|

编译器似乎没有找到类“ContextGraphics::Helper”的实现,这只会引发一个问题,即带有 pimpl 的类是否可以使用带有 pimpl 的对象。如果不将所有实现都放在 Window 类源文件中,这可能吗?对我来说,这似乎不是一个合理的解决方案。

4

1 回答 1

0

这是可能的,但有点复杂。

首先,您应该为ContextGraphics 之类的类使用指针。

所以,这个领域看起来像

ContextGraphics* graphics_context;

它只适用于 windows 类之前的前向声明,就像这样

class ContextGraphics; // forward decl 

class Window{
public:
...
};

现在,为什么必须使用指针,以及编译器真正想要的:当编译器看到“ContextGraphics graphics_context;”时 它将尝试将该字段“打印”到 Windows 类中。为此,编译器应该知道 sizeof(ContextGraphics),它只有在类完全定义时才知道。

这就是为什么以下是不可能的:

class Aaa;
class Bbb;

class Aaa
{
int x;
Bbb b;
};

class Bbb
{
Aaa a;
int y;
};

(想一想:假设 sizeof(Aaa) 是 100,那么 sizeof(Bbb) 是 sizeof(Aaa)+4 是 104,但如果是这样,sizeof(Aaa::a) 也是 104,所以 sizeof(Aaa)真的是 108 - 这是圆圈的真正位置。)

现在,使用指针 (ContextGraphics* graphics_context;) 问题解决了,因为编译器知道它的大小,它对于任何指针都是恒定的。

因此,以下将起作用:

class Aaa;
class Bbb;

class Aaa
{
int x;
Bbb* b;
};

class Bbb
{
Aaa* a;
int y;
};

(对于 32 位系统,sizeof(Aaa) == 8 和 sizeof(Bbb) == 8)

事实上,pimplIdiom 本身要求大多数打印字段成为指针,因为这是编译器在不知道类本身的情况下知道字段大小(或函数参数的大小)的唯一方法。

顺便说一下(offtopic),您正在构建一个已经存在的库 - 称为 Qt,这可能会为您节省大量时间。如果是关于 gamedev - Qt 也提供了相当好的性能。

于 2013-07-21T11:21:01.720 回答