mardi 28 novembre 2017

Hiding implementation of members owned by PImpl-objects

I have a class which I want to create an interface for without showing any of the implementation (not because it's closed source, but because of a lot of unnecessary headers such as OpenGL coming with it), let's call that class Foo. I know this is commonly done through either the PImpl-idiom or virtual interfaces, and I've implemented the PImpl-idiom for that class.

However, this class has public functions which returns other classes that are also including those headers, so obviously I can't return those objects as-is as that'd require me to include headers I don't want to include in Foo.h. However, these classes are used internally within the library a lot, and thus I don't want to implement them with PImpl as that'd introduce a lot of overhead (they're used a lot within a 3D renderer's code). Let's call one of them Bar:

Foo.h:

#include "Bar.h"

class Foo
{

private:
    class impl;

    std::unique_ptr<impl> m_pimpl;

public:
    Foo();
    Bar& get_bar();
};

Foo.cpp:

#include "Foo.h"

class Foo::impl {
private:
    Bar m_bar;

public:
    Bar& get_bar();
};

Foo::Foo() : m_pimpl{std::make_unique<impl>()} 
{ }

Bar& Foo::get_bar() {
    return m_pimpl->get_bar();
}

For this to work I need to #include "Bar.h", but Bar.h might include headers I want to hide. This leaves me with the option to make Bar use the PImpl-idiom as well, but I don't want that overhead for Bar because Bar is used a lot internally within Foo. However, I figured a way to solve this, but I'm not very sure about it as I haven't seen it used anywhere before:

Using PImpl without an owning pointer/reference to simply wrap a class and create a interface for it. This way the overhead only applies when outside of Foo, but internally it'll still use the non-wrapped class.

For example, let's say PBar is wrapping Bar:

PBar.h:

class Bar;

class PBar {
private:
    Bar &m_bar;

public:
    explicit PBar(Bar &bar);
    void do_stuff();
};

PBar.cpp:

#include "PBar.h"

#include "../impl/Bar.h" // This is the actual Bar implementation


PBar::PBar(Bar &bar) : m_bar(bar) {

}

void PBar::do_stuff() {
    m_bar.do_stuff();
}

And Foo instantiates PBar on creation with a reference to the actual Bar inside the object:

Foo.h

#include "PBar.h"

class Foo
{

private:
    class impl;
    std::unique_ptr<impl> m_pimpl;
    PBar m_bar;

public:
    Foo();
    PBar& get_bar() { return m_bar; }
};

Foo.cpp:

class Foo::impl {
private:
    Bar m_bar;

public:
    Bar& get_bar();
};

Foo::Foo() : m_pimpl{std::make_unique<impl>()},
             m_bar(impl->get_bar())
{ }

Is this pattern ever used? Are there any other ways I can solve this problem? It's more or less the same principle as PImpl, but is there anything bad about it I haven't yet thought about? It definitely feels even less clean, but I can't see how this could be done in any other way.

Also, I want neither PBar or Bar to be constructable outside of Foo, so that's not a problem.

Thanks!

Aucun commentaire:

Enregistrer un commentaire