19

我想将 pimpl 成语与继承一起使用。

这是基础公共类及其实现类:

class A
{
    public:
      A(){pAImpl = new AImpl;};
      void foo(){pAImpl->foo();};
    private:
      AImpl* pAImpl;  
};
class AImpl
{
    public:
      void foo(){/*do something*/};
};

我希望能够使用其实现类创建派生的公共类:

class B : public A
{
    public:
      void bar(){pAImpl->bar();};    // Can't do! pAimpl is A's private.
};        

class BImpl : public AImpl
{
    public:
      void bar(){/*do something else*/};
};

但是我不能在 B 中使用 pAimpl,因为它是 A 的私有的。

所以我看到了一些解决方法:

  1. 在 B 中创建 BImpl* pBImpl 成员,并使用附加的 A 构造函数 A(AImpl*) 将其传递给 A。
  2. 将 pAImpl 更改为受保护(或添加 Get 函数),并在 B 中使用它。
  3. B 不应该从 A 继承。在 B 中创建 BImpl* pBImpl 成员,并在 B 中创建 foo() 和 bar(),这将使用 pBImpl。
  4. 还有什么办法吗?

我应该选择什么?

4

5 回答 5

8
class A
{
    public:
      A(bool DoNew = true){
        if(DoNew)
          pAImpl = new AImpl;
      };
      void foo(){pAImpl->foo();};
    protected:
      void SetpAImpl(AImpl* pImpl) {pAImpl = pImpl;};
    private:
      AImpl* pAImpl;  
};
class AImpl
{
    public:
      void foo(){/*do something*/};
};

class B : public A
{
    public:
      B() : A(false){
          pBImpl = new BImpl;
          SetpAImpl(pBImpl);
      };
      void bar(){pBImpl->bar();};    
    private:
      BImpl* pBImpl;  
};        

class BImpl : public AImpl
{
    public:
      void bar(){/*do something else*/};
};
于 2009-01-30T06:53:40.403 回答
2

我认为从纯粹的面向对象理论的角度来看,最好的方法是不要让 BImpl 从 AImpl 继承(这就是你在选项 3 中的意思吗?)。但是,让 BImpl 从 AImpl 派生(并将所需的 impl 传递给 A 的构造函数)也是可以的,只要 pimpl 成员变量是const. 使用 get 函数还是直接从派生类访问变量并不重要,除非您想在派生类上强制执行 const 正确性。让派生类改变 pimpl 不是一个好主意——它们可能会破坏 A 的所有初始化——而且让基类改变它也不是一个好主意。考虑对您的示例进行此扩展:

class A
{
protected:
   struct AImpl {void foo(); /*...*/};
   A(AImpl * impl): pimpl(impl) {}
   AImpl * GetImpl() { return pimpl; }
   const AImpl * GetImpl() const { return pimpl; }
private:
   AImpl * pimpl;
public:
   void foo() {pImpl->foo();}


   friend void swap(A&, A&);
};

void swap(A & a1, A & a2)
{
   using std::swap;
   swap(a1.pimpl, a2.pimpl);
}

class B: public A
{
protected:
   struct BImpl: public AImpl {void bar();};
public:
   void bar(){static_cast<BImpl *>(GetImpl())->bar();}
   B(): A(new BImpl()) {}

};

class C: public A
{
protected:
   struct CImpl: public AImpl {void baz();};
public:
   void baz(){static_cast<CImpl *>(GetImpl())->baz();}
   C(): A(new CImpl()) {}
};

int main()
{
   B b;
   C c;
   swap(b, c); //calls swap(A&, A&)
   //This is now a bad situation - B.pimpl is a CImpl *, and C.pimpl is a BImpl *!
   //Consider:
   b.bar(); 
   //If BImpl and CImpl weren't derived from AImpl, then this wouldn't happen.
   //You could have b's BImpl being out of sync with its AImpl, though.
}

尽管您可能没有 swap() 函数,但您可以很容易地想到会发生类似的问题,特别是如果 A 是可分配的,无论是偶然还是有意。这有点微妙地违反了 Liskov 可替代性原则。解决方案是:

  1. 请勿在施工后更改 pimpl 成员。将它们声明为AImpl * const pimpl. 然后,派生构造函数可以传递适当的类型,派生类的其余部分可以自信地向下转换。但是,您不能进行非抛出交换、分配或写时复制等操作,因为这些技术要求您可以更改 pimpl 成员。但是,如果您有继承层次结构,您可能并不真的打算做这些事情。

  2. 分别为 A 和 B 的私有变量提供不相关(和愚蠢)的 AImpl 和 BImpl 类。如果 B 想对 A 做某事,则使用 A 的公共或受保护接口。这也保留了使用 pimpl 的最常见原因:能够将 AImpl 的定义隐藏在派生类无法使用的 cpp 文件中,因此当 A 的实现发生更改时,一半的程序不需要重新编译。

于 2009-01-29T13:37:37.857 回答
1

正如 stefan.ciobaca 所说,如果您真的希望 A 是可扩展的,那么您会希望pAImpl受到保护。

但是,您在Bof中的定义void bar(){pAImpl->bar();};似乎很奇怪,bar方法 onBImpl和 not 也是如此AImpl

至少有三个简单的替代方案可以避免这个问题:

  1. 您的替代方案 (3)。
  2. (3) 的变体,其中BImplextends AImpl(继承现有实现foo而不是定义另一个),BImpl定义barB使用其私有BImpl* pBImpl来访问两者。
  3. 委托,其中B持有指向每个的私有指针,并将每个AImplBImpl转发给适当的实现者。foobar
于 2009-01-29T13:18:47.240 回答
0

我会做(1),因为 A 的私人对 B 来说是或没有业务。

实际上,我不会按照您的建议将其传递给 A,因为 A 在 A::A() 中创建了自己的。从 Bis打电话pApimpl->whatever()也不合适(私人意味着私人)。

于 2009-01-29T11:59:19.087 回答
-4

正确的做法是做(2)。

一般来说,您可能应该考虑让所有成员变量默认受保护而不是私有。

大多数程序员选择私有的原因是他们不考虑其他想要从他们的类派生的人,并且大多数介绍性 C++ 手册都教授这种风格,因为所有示例都使用私有。

编辑

代码重复和内存分配是使用 pimp 设计模式的不良副作用,据我所知是无法避免的。

如果需要让 Bimpl 继承 Aimpl,并且希望通过 A 和 B 向它们公开一致的接口,则 B 也需要继承 A。

在这种情况下,您可以做的简化事情的一件事是让 B 从 A 继承,并且只更改构造器,以便 B::B(...) {} 创建 Bimpl,并为 Bimpl 的所有方法添加调度不在 Aimpl 中。

于 2009-01-29T12:18:12.293 回答