繁体   English   中英

Pimpl成语与继承

[英]Pimpl idiom with inheritance

我想用继承的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. 还有其他方法吗?

我应该选择什么?

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

我认为从纯粹的面向对象理论的角度来看,最好的方法是不要让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的实现发生更改时,一半程序不需要重新编译。

正如stefan.ciobaca所说,如果你真的希望A可以扩展,那么你希望pAImpl得到保护。

但是,你在Bvoid bar(){pAImpl->bar();}; 看起来很奇怪,因为barBImpl上的一种方法而不是AImpl

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

  1. 你的替代方案(3)。
  2. (3) BImpl扩展AImpl (继承foo的现有实现而不是定义另一个)的变体, BImpl定义了barB使用其私有BImpl* pBImpl来访问它们。
  3. 委托,其中B保存每个AImplBImpl私有指针,并将foobar中的每一个转发给适当的实现者。

我会这样做(1)因为A的私人是否是B的业务。

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

正确的方法是做(2)。

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

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

编辑

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

如果你需要让Bimpl继承Aimpl并且你想通过A和B向它们公开一致的接口,那么B也需要继承A.

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM