Pimpl成语与继承

时间:2009-01-29 11:44:29

标签: c++ inheritance oop pimpl-idiom

我想使用带继承的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. 还有其他方式吗?
  5. 我应该选择什么?

5 个答案:

答案 0 :(得分:7)

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*/};
};

答案 1 :(得分:2)

我认为从纯粹的面向对象理论的角度来看,最好的方法是不要让BImpl从AImpl继承(你在选项3中的意思是什么?)。但是,如果pimpl成员变量为const,那么让BImpl从AImpl派生(并将所需的impl传递给A的构造函数)也可以。除非要在派生类上强制执行const-correctness,否则使用get函数或直接从派生类访问变量并不重要。让派生类改变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的最常见原因:能够在派生类无法使用的cpp文件中隐藏AImpl的定义,因此当A的实现发生更改时,一半的程序不需要重新编译。

答案 2 :(得分:1)

正如stefan.ciobaca所说,如果你真的希望A可以扩展,你需要保护pAImpl

但是,B void bar(){pAImpl->bar();};中的定义似乎很奇怪,因为barBImpl而不是AImpl的方法。

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

  1. 您的替代方案(3)。
  2. (3)的变体BImpl扩展AImpl(继承foo的现有实施而不是定义另一个),BImpl定义barB使用其私有BImpl* pBImpl来访问这两个。
  3. 委派,其中B包含AImplBImpl中每一个的私人指针,并将foobar中的每一个转发给相应的实施者。< / LI>

答案 3 :(得分:0)

我会这样做(1)因为A的私人是B的业务或者没有业务。

实际上我不会按照你的建议将它传递给A,因为A在A :: A()中自己创建。 从Bis调用pApimpl->whatever()也不合适(私有表示私有)。

答案 4 :(得分:-3)

正确的方法是做(2)。

通常,您应该考虑使默认情况下保护所有成员变量而不是私有。

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

修改

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

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

在这种情况下,你可以做的一件事就是让B继承自A并且只改变构造函数,使B :: B(...){}创建一个Bimpl,并为所有方法添加调度Bimpl不在Aimpl。