【问题标题】:Can a class with pimpl use an object with pimpl?带有 pimpl 的类可以使用带有 pimpl 的对象吗?
【发布时间】:2013-07-21 09:27:22
【问题描述】:

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 类源文件中,是否可以这样做?对我来说,这似乎不是一个合理的解决方案。

【问题讨论】:

  • 你遇到了一个普通的循环依赖问题。在这个网站和其他网站上有大量关于此的信息。 Pimpl 与它无关。
  • @Mat:哦,很高兴为这个问题起个名字。谢谢!这有助于找到可能的解决方案,如果确实存在的话。
  • 好吧,问题仍然存在——这可能吗?如果可以,怎么做?

标签: c++11 forward-declaration unique-ptr pimpl-idiom


【解决方案1】:

有可能,但有点复杂。

首先,您应该为 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 本身要求大部分打印字段成为指针,因为这是编译器在不知道类本身的情况下知道字段大小(或函数参数的大小)的唯一方法。

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

【讨论】:

    猜你喜欢
    • 2010-10-04
    • 1970-01-01
    • 2014-08-30
    • 2017-09-09
    • 2018-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多