如何仅将实现暴露给pimpl习语中的一组指定类?

时间:2014-03-24 13:45:43

标签: c++

我们的class A有一个内class A::Implclass AllowedToAccess应该能够从A::Impl&获得class A,其他任何类都不应该这样做,甚至不知道class A::Impl存在。

class A_1
{
public:
    class Impl;

    Impl& impl();
    const Impl& impl() const;

private:
    std::unique_ptr<Impl> m_impl;
};

class A_2
{
private:
    friend class AllowedToAccess;

    class Impl;
    std::unique_ptr<Impl> m_impl;
};

class A_3
{
private:
    friend class AllowedToAccess;

    class Impl;

    class ImplContainer
    {
        friend class A_3;
        std::unique_ptr<Impl> impl;
    } m_implContainer;

    Impl& impl(); // return *m_implContainer.impl;
    const Impl& impl() const; // return *m_implContainer.impl;
};

以下是一些代码来说明我的想法。

A_1

优点:m_impl是安全的。

缺点:不应该知道A::Impl的课程会知道它(尽管他们不知道A::Impl到底是什么意思。)

A_2

优点:不应该了解A::Impl的课程也不会知道。

缺点:m_impl不受保护(AllowedToAccess可能只是将其设置为nullptr)。

A_3

优点:不应该了解A::Impl的课程不会知道,m_impl是安全的。

缺点:ImplContainer的Boilerplate代码。

有更好的想法吗?

编辑:提出这样的解决方案。

template <typename T>
class Accessor
{
private:
    friend class AllowedToAccess;

    typedef typename T::Impl impl_t;

    static typename T::Impl& impl(T& t)
    {
        return *t.m_impl;
    }

    static const typename T::Impl& impl(const T& t)
    {
        return *t.m_impl;
    }
};

class A
{
private:
    friend class Accessor<A>;

    class Impl;
    std::unique_ptr<Impl> m_impl;
};

class A::Impl
{
public:
    void f() const
    {

    }
};

class AllowedToAccess
{
public:
    AllowedToAccess()
    {
        const A a;

        const Accessor<A>::impl_t& impl = Accessor<A>::impl(a);

        impl.f();
    }
};

有一个代码实现层,所有这些类都将作为朋友添加到Accessor。

为了进一步隐藏内容,访问者只能在公共代码中向前声明为template <typename T> class Accessor;。并在私人代码中定义。

3 个答案:

答案 0 :(得分:2)

如果可以将所有使用m_impl本身的逻辑移动到基类,则可以使m_impl对派生类不可访问,但允许它引用Impl,然后使{{{ 1}}类派生类的朋友:

Access

答案 1 :(得分:1)

这个怎么样:

class A_4
{
public:
    class Impl;

    Impl& impl();
    const Impl& impl() const;

private:
    std::unique_ptr<Impl> m_impl;
};

class A_4::Impl
{
private:
    friend class A_4;
    friend class AllowedToAccess;

    Impl();
    ~Impl();
    Impl(const Impl&) = delete;
    Impl& operator=(const Impl&) = delete;

    // All members private!
};

如果代码可以调用impl()或编写A_4::Impl,如果没有任何用处可以实际使用它,那么这并不重要。

答案 2 :(得分:1)

我不知道它是否是一个更好的主意,但你可以提供自己的AllowedToAccess实现,这将安全地暴露所需的接口。我称之为Accessor

class Base{
  double priv = 0;
public:
  friend class Accessor;
};

class Accessor {
public:
  int& priv(Base& b) { return b.priv; };
};

现在,类可以通过Accessor访问公开的私有。语义稍有改变,因为你像accessor.priv(b) = something一样使用它,但是你可以完全控制暴露的接口。我认为此变体中的Accessor可能具有所有静态方法,因为无论如何都会传递被访问的对象。所以语义是Accessor::priv(b)

我想这是 A_3 示例的变体,代码已移至朋友类。但它不会污染A_类。

另一种选择是以简单包装的形式提供您自己的Accessor类。

class Accessor {
public:
  Accessor(Base b) _b(b);
  int priv() { return _b.priv; };
  int priv(int val) { _b.priv = val; };
  int& priv_r() { return _b.priv; };
private:
  Base& _b;
};

此处的界面也可完全自定义,您可以访问以下功能:

Base b;
Accessor interface(b);
interface.priv(42);

你也可以绝对安全地禁止从Accessor派生(有一些技巧),所以没有人可以通过Accessor的邪恶实现来派生和搞砸你的对象。我认为这会有点偏执。