【问题标题】:Hiding implementation of members owned by PImpl-objects隐藏 PImpl 对象拥有的成员的实现
【发布时间】:2018-05-12 04:09:51
【问题描述】:

我有一个类,我想为它创建一个接口而不显示任何实现(不是因为它是封闭源代码,而是因为它附带了许多不必要的头文件,例如 OpenGL),让我们称这个类为 Foo。我知道这通常是通过 PImpl-idiom 或虚拟接口完成的,并且我已经为该类实现了 PImpl-idiom。

但是,这个类具有返回其他类的公共函数,这些类也包含这些标头,所以显然我不能按原样返回这些对象,因为这需要我包含我不想包含的标头呸。但是,这些类在库内部经常使用,因此我不想用 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 在创建时使用对对象内部实际 Bar 的引用来实例化 PBar:

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 差不多,但有什么不好的地方我还没想好?它确实感觉更不干净,但我看不出如何以任何其他方式做到这一点。

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

谢谢!

【问题讨论】:

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


    【解决方案1】:

    您不能(不应该)更改引用成员引用的对象:您在此处如何操作:Foo a,b; a=b;(假设您初始化了一个非空的 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));
    

    【讨论】:

    • 感谢您的反馈!我没有想到这一点,但如果我要使用需要我手动设置一些偏移量/大小的东西,我只会选择“快速 pimpl”习语并提高效率,而不必担心接口! (gotw.ca/gotw/028.htm)
    • 我认为这超出了主题,但这仍然很有趣,bonne chance jeune graçon!
    猜你喜欢
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-07
    相关资源
    最近更新 更多