Pregunta

I'm planning out a design for my Window class. The goal is to provide an abstraction for creating a platform agnostic window ready for OpenGL rendering. I'm thinking of having a class 'Window' be the public interface, and a 'WindowImpl' class handle the work. Would making Window a friend of WindowImpl and calling WindowImpl functions inside Window cause issues? Technically, WindowImpl wouldn't be instantiated correct? So the destructor wouldn't be called which means the Window destructor wouldn't be called so a destroy function would be needed. Ex.

class MyWindow
{
    public:
        void create(width, height, title)
        {
            WindowImpl::create(width, height, title);
            open = true;
        }

        void close()
        {
            WindowImpl::destroy();
            open = false;
        }

        bool isOpen()
        {
            return open;
        }

    private:
        bool open;
};

class WindowImpl
{
    friend class MyWindow;

    private:
        static void create(width, height, title) {} // Creates the window
        static void destroy()
        {
            XCloseDisplay(display);
        }

        static Display* display;
        static Window window;
        static GLXContext context;
};

I don't know if I'm going in the right way with this, or if I'm making things more complicated then they need to be. Since a different WindowImpl will be compiled depending on the target platform, I want to keep as much of it away from the user as possible, keeping all data like the window title and resolution inside of the Window class and any changes that are necessary can be made without the WindowImpl keeping track of anything more then the implementation specific stuff.

¿Fue útil?

Solución

If it really has to be platform-agnostic, then my suggestion is to use something like this:

class WindowImpl
{
public:
    virtual void setOwner(Window* pOwner) = 0
    virtual void create(width, height, title) = 0;
    virtual void close() = 0;
};

class Window
{
public:

    void create(width, height, title)
    {
        mImpl->create(width, height, title);
        open = true;
    }

    void close()
    {
        open = false;
        mImpl->destroy();
    }

    Window(std::unique_ptr<WindowImpl> pImpl)
        : mImpl(pImpl)
    {
    }

private:
    std::unique_ptr<WindowImpl> mImpl; 
};

// Then off somewhere else...
class WindowImplX11 : public WindowImpl
{
public:
    void setOwner(Window* pOwner) {
        mOwner = pOwner;
    }

    void destroy() {
        XCloseDisplay(display);
    }


private:
    // Pointer back to the owner just in case (e.g. event
    // delivery); if weak_ptr worked with unique_ptr, that
    // would be better.
    Window* mOwner;

    Display* display;
    GLXContext context;
};

This is a light version of the Bridge pattern, which is commonly used when you have two incompatible object hierarchies which you need to link together. This is a degenerate case (since the "hierarchy" has only one class in it), but it's still a useful technique to think about. Probably the most famous example of this is Java AWT (however AWT calls it "Peer" rather than "Impl").

Exactly how you split the responsibilities between the front end and back end is, of course, something that you need to decide for yourself, and there will probably be some back-and-forth. You may, for example, decide that an OpenGL context is a sufficiently important concept that you need to expose it to clients. The same goes for things like vsync fences which aren't fully supported in the standard way yet. Yes, I'm looking at you, OS X.

The only catch is how you construct a Window and its Impl. The traditional way is to use an abstract factory:

class Toolkit
{
public:
    std::unique_ptr<Window> createWindow() = 0;
};

// then elsewhere...

// Should have been in the library, only left out by oversight.
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args )
{
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

class ToolkitX11 : public Toolkit
{
public:
    std::unique_ptr<Window>
    createWindow() {
        return make_unique<Window>(make_unique<WindowImplX11>());
    }
};

There are other ways which are a little more Modern.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top