C++:无法弄清楚如何正确隐藏实现细节

C++: Can't figure out how to hide implementation details properly

我有以下设置:

foo.h:

class A {
    friend class B;
private:
    A() {}
};

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    vector<A> myMember;
};

A 的对象永远不会暴露给任何程序,包括 foo.h。带有 A 的矢量仅用于帮助 B 发挥其计算适配器的作用。通过将 A 的构造函数设为私有,我认为我可以避免其他编译单元使用它,而且它似乎可以工作。然而,问题出在

foo.cpp

void B::computeResult(Result &r) {
    MyCustomStorage<A> storage;
    A *a = storage.allocate(); // error: "A::A() is private"
}

MyCustomStorage 的一部分看起来像这样:

template <typename T>
class MyCustomStorage {
    T *allocate() {
        ...
        T *ptr = new T[count]; // error: "A::A() is private"
        ...
    }
};

但我想既然 allocate() 是从成员函数中调用的,就不会发生这种情况!我该如何解决这个问题?

A 成为 MyCustomStorage 的朋友似乎很简单。使 A 成为 B 的私有嵌套 class 会使 foo.cpp 中的各种帮助-class 失败,因为 "A is private".

那么解决这个问题最干净的方法是什么?

解决方案

我最终采用了@potatoswatter 的第二个解决方案,并进行了以下适当的更改:

foo.h

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    class A {
    private:
        A() {}
    };
    class Helper; // forward declared!
    vector<A> myMember;
};

foo.cpp

class B::Helper {
    int help(A& a) { return 42; } // no problem! Helper is a member of B
}

void B::computeResult(Result &r) {
    MyCustomStorage<A> storage;
    A *a = storage.allocate(); // no problem! A is a member of B
    Helper h;
    h.help(*a); // no problem!
}

私有的不是A的构造函数,而是整个class。

最好的解决方案是创建一个 "private" 命名空间。 C++ 没有名称空间级别的访问保护,但可以合理预期用户不会访问不熟悉的名称空间。

namespace impl {
struct A {
    A() {}
};
}

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    vector<impl::A> myMember;
};

另一种方法是让 A 成为 B 的成员。这以更深的嵌套为代价提供了 "real" 访问保护。我个人更喜欢第一种解决方案,避免嵌套 classes.

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    struct A {
        A() {}
    };

    vector<A> myMember;
};

任何需要 A 的帮助者都需要成为朋友。有多种解决方法,例如在具有 protected 访问权限的基础 class 中嵌套 A,但实际上,namespace impl 提供的妥协最少。

恕我直言,您有两种选择。您可以 1) 使用 Pimpl 习语,或者 2) 您可以使用前向声明。

Pimpl 成语示例:

class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    class Impl;
    Impl *pimpl;
};

并且在您的 *.cpp 文件中,您可以定义 Impl class 并使用它的内容。

class B::Impl {
public:
    std::vector<A> stuff;
}

B::B() : pimpl(new Impl) {
}

B::~B() {
    delete pimpl;
}

void B::AddObject(Object &o) {
    pimpl->stuff.Fx(o);
}

Pimpl 惯用语也可以使用智能指针,我只是为了 clarity/brevity 而没有在这里。

如果 AB

在同一个命名空间中,也可以使用前向声明
class B {
public:
    void addObject(Object &o); // adds to myMember; A is not exposed!
    void computeResult(Result &r); // uses myMember to compute result
private:
    std::vector<class A*> myMember;
};

但是这个习惯用法与您的要求根本不同,它限制您在对象中使用指针 myMember,您可能不想这样做。内联定义 class A* 也是一种非标准的前向声明方法。当然,使用智能指针可以减少此位置内存泄漏的可能性。