質問

Pimpl is short for "pointer to implementation" and offers a handy way to hide away implementations in classes. I'm implementing a Window-class, which hides platform-specific functions and structures from user of this class, and so the class interface ends up looking quite clean:

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;
};

Behind the scenes are all those nasty WINAPI calls and such, and when I'm possibly aiming towards wider range of supported platforms, the class header does not need to change at all, only the source file. Very useful, no need for that much rewriting!

However, this object appears to be my problem (inside the class):

ContextGraphics graphics_context;

It's a Direct3D/OpenGL graphics context (user of "ContextGraphics" can determine that) and as you might have guessed, it uses pimpl-idiom as well:

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);
};

The problem I'm facing is the compiler:

Window.cpp|145|error: invalid use of incomplete type 'class ContextGraphics::Helper'|

ContextGraphics.hpp|43|error: forward declaration of 'class ContextGraphics::Helper'|

The compiler does not seem to find the implementation of class "ContextGraphics::Helper", which only raises the question if a class with pimpl can use an object with pimpl at all. Is this possible to do without putting all the implementations inside the Window-class source file? To me it doesn't seem to be a reasonable solution.

役に立ちましたか?

解決

It is possible, but a bit more complex.

Firstly you should use pointers for classes like ContextGraphics.

So, the field will look like

ContextGraphics* graphics_context;

And it will work just with a forward declaration before windows class, like this

class ContextGraphics; // forward decl 

class Window{
public:
...
};

Now, why is this is it necessary to use a pointer, and what the compiler really wants: When compiler see "ContextGraphics graphics_context;" it will try to 'inprint' the field into class Windows. To do it, compiler should know sizeof(ContextGraphics), which is only known when the class is fully defined.

That's why the following is impossible:

class Aaa;
class Bbb;

class Aaa
{
int x;
Bbb b;
};

class Bbb
{
Aaa a;
int y;
};

(Just think of it: suppose sizeof(Aaa) is 100, then sizeof(Bbb) is sizeof(Aaa)+4 is 104, but if so, sizeof(Aaa::a) is 104 too, and so sizeof(Aaa) is really 108 - this is where the circle really is.)

Now, using a pointer (ContextGraphics* graphics_context;) problem is solved, because compiler knows it's size, it's constant for any pointers.

So, the following will work:

class Aaa;
class Bbb;

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

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

(sizeof(Aaa) == 8 and sizeof(Bbb) == 8, for 32bit system)

In fact pimplIdiom itself requires most of inprinted fields to become pointers, because it's the only way for a compiler to know the fields size (or function params' sizes) without knowing the class itself.

And by the way (offtopic), you are building a library which already exists - called Qt, probably this will save you a lot of time. If it's about gamedev - Qt offers quite good perfomance also.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top