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

Posted

技术标签:

【中文标题】C++:无法弄清楚如何正确隐藏实现细节【英文标题】:C++: Can't figure out how to hide implementation details properly 【发布时间】:2015-11-03 18:42:26 【问题描述】:

我有以下设置:

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 的私有嵌套类会使foo.cpp 中的各种帮助类失败,因为“A 是私有的”。

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

解决方案

我最终选择了@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!

【问题讨论】:

见***.com/questions/8972588/… B 成为A 的朋友似乎很好。 【参考方案1】:

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

最好的解决方案是创建一个“私有”命名空间。 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 的成员。这以更深的嵌套为代价提供了“真正的”访问保护。我个人更喜欢第一种解决方案,并避免嵌套类。

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 的助手都需要成为朋友。有多种解决方法,例如将 A 嵌套在具有 protected 访问权限的基类中,但实际上,namespace impl 提供的妥协最少。

【讨论】:

我更喜欢第二个。客观上更好,因为它封装了A,而第一个版本没有。我不希望人们不访问不熟悉的命名空间,namespace std; 曾经不熟悉,但仍然每个人都是 using @nwp 但是像std::__1std::__function 这样的命名空间是陌生的。包括所有标准库实现、Boost 等在内的大型流行库往往更喜欢第一种方法,否则不会为了访问保护而扭曲类。 @potatoswatter 我使用了第二种解决方案,在我的第一篇文章中进行了编辑。我很想知道您是否提到了扭曲问题。 @bombax 当然可以。如果有任何“扭曲”,那只是B 正在捕获附加功能,就好像它是一个命名空间一样。如果出现问题,您可以随时重构嵌套类。【参考方案2】:

恕我直言,您有几个选择。您可以 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 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 习语使用智能指针,我只是为了清晰/简洁而没有在这里。

如果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* 也是一种非标准的前向声明方法。当然,使用智能指针会降低该位置内存泄漏的可能性。

【讨论】:

vector&lt;class A*&gt; 在功能上与vector&lt;A&gt; 不同,它是一种容易发生内存泄漏的反模式。 vector&lt;class A&gt; 和不完整的class A 将在实践中起作用,并且它被定为标准化,但尚未正式祝福。 @Potatoswatter 我注意到vector&lt;class A*&gt; 是根本不同的。我将对非标准的前向声明方法添加警告。感谢您的评论。 他可以使用带有std::unique_ptr的部分特化来使用pimpl idiom并避免原始指针可能出现的内存泄漏。

以上是关于C++:无法弄清楚如何正确隐藏实现细节的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ionic 中使用后端实现主细节模式?

如何在 C++ 共享库中隐藏业务对象的实现细节并提供接口

如何正确更新 PyTorch 中的权重?

无法弄清楚如何正确设置 DropdownButton 的样式

无法弄清楚如何正确存储命令行参数[关闭]

无法弄清楚如何使用 CLPlacemark 将我当前的位置正确嵌入到消息正文中