1

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!

André T.
  • 328
  • 3
  • 13

1 Answers1

0

You cannot (should not) change the object referenced by a reference member: how do you do here: Foo a,b; a=b; (supposing you initialize a non null unique_ptr). This is easily corrected replacing the reference by a pointer.

This look like a good idea, what you do is caching a dereferencement. But you are loosing some efficiency of the pimpl idiom and you are doubling the size of Foo.

Have you thought in making the class impl standard layout and putting Bar at a known offset inside impl:

Foo.h

constexpr auto impl_bar_offset = 8;
//...
class Foo{
  private:
    class impl;
    std::unique_ptr<impl> m_impl;
  public:
    bar& get_bar(){
       assert(m_impl);
       return *reinterpret_cast<bar*>(
         reinterpret_cast<unsigned char*>(m_impl.get())+impl_bar_offset);
    }
 };

Foo.cpp

class impl{
  long a_long;
  bar a_bar;
  //...
  };
static_assert(impl_bar_offset==offsetof(impl,a_bar));
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Thanks for the feedback! I hadn't thought about that, but if I were to use something that requires me to manually set some offset/size, I'd just go for the "fast pimpl" idiom instead and gain efficiency AND not have to worry about interfaces! (http://www.gotw.ca/gotw/028.htm) – André T. Nov 28 '17 at 18:23
  • I think this is out of the subject but this is still interesting, bonne chance jeune graçon! – Oliv Nov 28 '17 at 18:32