隐藏PImpl-objects所拥有成员的实施

时间:2017-11-28 16:09:23

标签: c++ design-patterns interface shared-libraries pimpl-idiom

我有一个类,我想创建一个接口而不显示任何实现(不是因为它是封闭的源代码,而是因为有很多不必要的标题,比如OpenGL随之而来),让我们调用该类Foo。我知道这通常是通过PImpl-idiom或虚拟接口完成的,我已经为该类实现了PImpl-idiom。

但是,这个类有公共函数,它返回其他类,也包括那些标题,所以显然我不能按原样返回那些对象,因为它要求我包含我不想包含的标题Foo.h.但是,这些类在库内部使用很多,因此我不想用PImpl实现它们,因为它会引入很多开销(它们在3D渲染器的代码中被大量使用)。我们打电话给其中一个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();
}

为了实现这个目的,我需要#include“Bar.h”,但是Bar.h可能包含我想要隐藏的标题。这让我可以选择让Bar使用PImpl-idiom,但我不希望Bar的开销,因为Bar在Foo内部使用很多。但是,我想办法解决这个问题,但是我不太确定它,因为我之前没有看到它曾经使用过:

使用PImpl而不使用拥有指针/引用来简单地包装类并为其创建接口。这样,开销只适用于Foo的外部,但在内部它仍将使用非包装类。

例如,假设PBar正在包装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();
}

Foo在创建时实例化PBar,并引用对象中的实际Bar:

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())
{ }

这种模式曾被使用过吗?还有其他方法可以解决这个问题吗?它与PImpl的原理或多或少相同,但有什么不好的我尚未考虑过吗?它肯定感觉更不干净,但我看不出如何以任何其他方式做到这一点。

另外,我希望PBo或Bar都不能在Foo之外构建,所以这不是问题。

谢谢!

1 个答案:

答案 0 :(得分:0)

您不能(不应该)更改引用成员引用的对象:您如何在此处执行:Foo a,b; a=b;(假设您初始化非null unique_ptr)。通过指针替换引用很容易纠正。

这看起来是个好主意,你所做的就是缓存一个解除引用。但是你正在失去一些pimpl惯用语的效率,而你正在将Foo的大小加倍。

您是否考虑过制作class impl标准布局并将Bar放在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));