【问题标题】:Private members in pimpl class?pimpl 类的私人成员?
【发布时间】:2010-02-02 06:47:10
【问题描述】:

pimpl 惯用语中使用的实现类是否有任何理由拥有任何私有成员?我真正能想到的唯一原因是保护自己免受自己的伤害——即私有成员用于执行类和用户之间的某种契约,在这种情况下,类和用户是密切相关的,所以它似乎没有必要。

【问题讨论】:

  • 假设一个只读字符串的池化实现,实现类可以很好地进行引用计数,以便在需要时知道何时释放内存。
  • dirkgently:我想,为了对字符串的所有引用进行单一计数,该计数必须集中存储在字符串中,而不是包装器中。我在这里错过了什么?
  • @Steven Sudit:因为它会违反设计——严格来说,引用计数不是表示字符串所需的数据结构的一部分。
  • @Dirk:Plauger 的 STL 中的引用计数字符串违反了设计,因为计数与字符一起存储。虽然可以添加另一层,但这是否意味着计数字符串指向一个共享计数器结构,该结构本身指向一个原始字符串?只要计数器结构从不与其他结构实例共享字符串,这才是安全的。
  • @Steven Sudit:另一层?不明白你的意思。无论如何,我还没有看到该代码,我想一定有充分的理由以这种方式实现它——所以任何评论都是无关紧要的。无论如何,标准库确实有时会使用一些技巧来提高效率。

标签: c++ pimpl-idiom


【解决方案1】:

我认为人们将 Pimpl idiom 与 Adapter/Bridge/Strategy patterns 混淆了。习语是特定于一种语言的。模式可以适用于多种语言。

Pimpl 习惯用法旨在解决 C++ 中的以下问题: 类的私有成员在类声明中可见,这为类的用户添加了不必要的 #include 依赖项。这个成语也被称为编译器防火墙

如果实现是直接写在外部类对应的 *.cpp 文件中,并且在模块外无法访问,那么我认为简单地为 Pimpl 类使用结构体就可以了。为了进一步强化实现并不意味着直接重用的想法,我将它们定义为私有内部结构:

// foo.h
class Foo : boost::noncopyable
{
public:
   ...

private:
   struct Impl;
   boost::scoped_ptr<Impl> impl_;
};

// foo.cpp
struct Foo::Impl
{
   // Impl method and member definitions
};

// Foo method definitions

一旦有了实现类的头文件,我想我们就不再谈论 Pimpl 成语了。我们说的是Adapter、Bridge、Strategy、接口类等等……

只要我的 2 美分。

【讨论】:

    【解决方案2】:

    取决于您对 pImpl 的实现——特别是在您强制类不变量的地方,但总的来说,我认为 impl 部分不需要具有受保护/私有成员。事实上,我通常将它声明为一个结构体。

    【讨论】:

      【解决方案3】:

      理论上,pimpl 类仍然只是一个与其他类一样的类。它是接口的具体实现并不意味着其他代码不是 pimpl 类本身的客户端。

      也就是说,在实践中,我发现 pimpl 类往往更接近于具有一些成员函数的结构,而不是完整的对象,并且不需要将接口与实现分开。

      【讨论】:

        【解决方案4】:

        为什么它不应该有私人成员?仅仅因为您将一个接口定义为 PIMPL 并不意味着您在其他任何时候都不想使用该类。

        它仍然是一个类。数据可能应该是私有的或受保护的。对公众、私人或受保护者永远无法访问的数据的操作。您可能希望公开、保护或公开的操作。

        【讨论】:

        • 它实际上是一个“私有”类——只能在一个 cpp 文件中访问。在大多数情况下,C 风格的结构就可以满足此目的。
        • @Steven。不知道我明白你的意思。如果您将现有的类更改为使用 pImpl,您将保持原始类的接口不变,并将所有私有数据移动到仅在实现 cpp 文件中可见的结构,对吗?
        • @xzqx:为什么要写这么多访问器?您的所有数据实际上是否可供公众使用?为什么类内部没有操作?
        • @Steven:你的用例不会是适配器或桥接模式,而不是 Pimpl 吗?
        • @Emile:也许吧。或者它们可能有些正交,pimpl 是一种特定于语言的技术,可用于实现更通用的适配器/桥接模式(但也可用于实现其他模式)。
        【解决方案5】:

        我能真正想到的唯一原因 是为了保护自己免受自己的伤害

        这就是为什么首先存在“私有”和“受保护”的原因。当然你应该在你的实现中使用它们——我唯一不会的情况是实现没有行为(在这种情况下它不是真正的实现)。

        【讨论】:

          【解决方案6】:

          (我误解了这个问题,所以我正在改变我的答案。)

          带有 pimpl 的类所指向的实现类应该是一个普通类,与其他任何隐藏私有细节的理由一样多。事实上,它通常是一个预先存在的类,后来添加了 pimpl 层以打破依赖关系,并可能稍微简化接口。

          【讨论】:

          • 预先存在的类是“外部”类,而不是“impl”部分。
          • 我看到它是双向的。有时 pimpl 是一种将一致的接口置于可能发生变化的实现之上的方法。其他时候,pimpl 是一种完全隐藏实现的方法,打破了编译时的依赖关系。事实上,两者都可以是目标。
          【解决方案7】:

          因为private 意味着更好的数据封装。

          我知道这看起来很傻,但是我们有一种在工作中定义接口的方法非常简单:

          class Interface;
          
          class Concrete { public: .... private: Interface* m_interface; }
          
          class ConcreteFoo: public Interface {};
          

          主要优势:您可以随时更换另一个ConcreteBar。它实际上是PimplStrategy 模式的组合,Concrete 的构造函数会调用一个Factory,负责为有效对象提供服务。

          如果你想不出第二种实现 heart 的方法,只需将它封装在一个类中即可。这样,如果您以后必须重构,您只需要使用完全相同的方法集编写一个抽象 Interface,更改一些指针(和构造函数)就可以了;)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-09-18
            • 2022-11-13
            • 1970-01-01
            • 1970-01-01
            • 2011-06-08
            • 1970-01-01
            • 2015-07-16
            • 1970-01-01
            相关资源
            最近更新 更多