2013-07-21 6 views
0

Pimpl не подходит для «указателя на реализацию» и предлагает удобный способ скрыть реализации в классах. Я реализации оконного класса, который скрывает от платформы функций и структур от пользователей данного класса, и поэтому интерфейс класса заканчивает тем, что довольно чистый:Может ли класс с pimpl использовать объект с помощью pimpl?

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; 

Это/OpenGL Графический контекст Direct3D (пользователь "ContextGraphics" может определить, что) и как вы мог бы догадаться, он использует Pimpl-идиомы, а также:

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

проблема я столкнулся это компилятор:

Компилятор, похоже, не находит реализацию класса «ContextGraphics :: Helper», что ставит вопрос только в том случае, если класс с pimpl может использовать объект с pimpl вообще. Можно ли обойтись без размещения всех реализаций в исходном файле класса Window? Для меня это не кажется разумным решением.

+2

У вас есть обычная проблема циклической зависимости. На этом сайте есть много информации об этом. Пимпл не имеет к этому никакого отношения. – Mat

+0

@Mat: О, хорошо, чтобы иметь имя для этой проблемы. Благодаря! Это помогает найти возможное решение, если оно существует. – Helixirr

+0

Ну, вопрос все еще остается - это возможно, и если да, то как? – Helixirr

ответ

0

Возможно, но немного сложнее.

Во-первых, вы должны использовать указатели для классов, таких как ContextGraphics.

Таким образом, поле будет выглядеть

ContextGraphics* graphics_context; 

И это будет работать только с передним декларирование перед классом окна, как этот

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 (Ааа) составляет 100, то SizeOf (ГЭБ) является SizeOf (Ааа) +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; 
}; 

(SizeOf (Ааа) == 8 и SizeOf (ГЭБ) == 8, для 32-разрядной системы)

На самом деле сама pimplIdiom требует большинства inprinted fields, чтобы стать указателями, потому что это единственный способ для компилятора узнать размер полей (или размеры параметров функции), не зная самого класса.

И кстати (offtopic) вы строите библиотеку, которая уже существует - называется Qt, вероятно, это сэкономит вам много времени. Если речь идет о гамедеве - Qt предлагает неплохую производительность.