简体   繁体   English

Pimpl成语与继承

[英]Pimpl idiom with inheritance

I want to use pimpl idiom with inheritance. 我想用继承的pimpl习语。

Here is the base public class and its implementation class: 这是基础公共类及其实现类:

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

And I want to be able to create the derived public class with its implementation class: 我希望能够使用其实现类创建派生公共类:

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

But I can't use pAimpl in B because it is A's private. 但我不能在B中使用pAimpl,因为它是A的私有。

So I see some ways to solve it: 所以我看到一些解决方法:

  1. Create BImpl* pBImpl member in B, and pass it to A with additional A constructor, A(AImpl*). 在B中创建BImpl * pBImpl成员,并使用附加的A构造函数A(AImpl *)将其传递给A.
  2. Change pAImpl to be protected (or add a Get function), and use it in B. 更改要保护的pAImpl(或添加Get函数),并在B中使用它。
  3. B shouldn't inherit from A. Create BImpl* pBImpl member in B, and create foo() and bar() in B, that will use pBImpl. B不应该继承自A.在B中创建BImpl * pBImpl成员,并在B中创建foo()和bar(),这将使用pBImpl。
  4. Any other way? 还有其他方法吗?

What should I choose? 我应该选择什么?

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

I think the best way from a purely object-oriented-theoretical perspective is to not make BImpl inherit from AImpl (is that what you meant in option 3?). 我认为从纯粹的面向对象理论的角度来看,最好的方法是不要让BImpl从AImpl继承(这是你在选项3中的意思吗?)。 However, having BImpl derive from AImpl (and passing the desired impl to a constructor of A) is OK as well, provided that the pimpl member variable is const . 但是,如果pimpl成员变量是const ,那么让BImpl从AImpl派生(并将所需的impl传递给A的构造函数)也可以。 It doesn't really matter whether you use a get functions or directly access the variable from derived classes, unless you want to enforce const-correctness on the derived classes. 除非要在派生类上强制执行const-correctness,否则使用get函数或直接从派生类访问变量并不重要。 Letting derived classes change pimpl isn't a good idea - they could wreck all of A's initialisation - and nor is letting the base class change it a good idea. 让派生类改变pimpl并不是一个好主意 - 它们可能会破坏A的所有初始化 - 也不会让基类改变它是一个好主意。 Consider this extension to your example: 考虑您的示例的此扩展:

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.
}

Although you might not have a swap() function, you can easily conceive of similar problems occurring, particularly if A is assignable, whether by accident or intention. 虽然您可能没有swap()函数,但您可以轻松地设想出现类似的问题,特别是如果A可以分配,无论是偶然还是意图。 It's a somewhat subtle violation of the Liskov substitutability principle. 这是对Liskov可替代性原则的一种微妙的违反。 The solutions are to either: 解决方案是:

  1. Don't change the pimpl members after construction. 施工后不要更换pimpl会员。 Declare them to be AImpl * const pimpl . 声明它们是AImpl * const pimpl Then, the derived constructors can pass an appropriate type and the rest of the derived class can downcast confidently. 然后,派生的构造函数可以传递适当的类型,并且派生类的其余部分可以自信地向下传播。 However, then you can't eg do non-throwing swaps, assignments, or copy-on-write, because these techniques require that you can change the pimpl member. 但是,您不能进行非投掷交换,分配或写入时复制,因为这些技术要求您可以更改pimpl成员。 However however, you're probably not really intending to do these things if you have an inheritance hierarchy. 但是,如果您有继承层次结构,那么您可能并不打算做这些事情。

  2. Have unrelated (and dumb) AImpl and BImpl classes for A and B's private variables, respectively. A和B的私有变量分别有不相关的(和哑的)AImpl和BImpl类。 If B wants to do something to A, then use A's public or protected interface. 如果B想要对A做某事,那么使用A的公共或受保护的接口。 This also preserves the most common reason to using pimpl: being able to hide the definition of AImpl away in a cpp file that derived classes can't use, so half your program doesn't need to recompile when A's implementation changes. 这也保留了使用pimpl的最常见原因:能够在派生类无法使用的cpp文件中隐藏AImpl的定义,因此当A的实现发生更改时,一半程序不需要重新编译。

As stefan.ciobaca said, if you really wanted A to be extendable, you'd want pAImpl to be protected. 正如stefan.ciobaca所说,如果你真的希望A可以扩展,那么你希望pAImpl得到保护。

However, your definition in B of void bar(){pAImpl->bar();}; 但是,你在Bvoid bar(){pAImpl->bar();}; seems odd, as bar is a method on BImpl and not AImpl . 看起来很奇怪,因为barBImpl上的一种方法而不是AImpl

There are at least three easy alternatives that would avoid that issue: 至少有三种简单的替代方案可以避免这个问题:

  1. Your alternative (3). 你的替代方案(3)。
  2. A variation on (3) in which BImpl extends AImpl (inheriting the existing implementation of foo rather than defining another), BImpl defines bar , and B uses its private BImpl* pBImpl to access both. (3) BImpl扩展AImpl (继承foo的现有实现而不是定义另一个)的变体, BImpl定义了barB使用其私有BImpl* pBImpl来访问它们。
  3. Delegation, in which B holds private pointers to each of AImpl and BImpl and forwards each of foo and bar to the appropriate implementer. 委托,其中B保存每个AImplBImpl私有指针,并将foobar中的每一个转发给适当的实现者。

I would do (1) because A's privates are or no business for B. 我会这样做(1)因为A的私人是否是B的业务。

Actually I would not pass it to A as you suggest, because A makes its own in A::A(). 实际上我不会像你建议的那样将它传递给A,因为A在A :: A()中自己创建。 Calling pApimpl->whatever() from Bis also not appropriate (private means private). 从Bis调用pApimpl->whatever()也不合适(私有意味着私有)。

The correct way is to do (2). 正确的方法是做(2)。

In general, you should probably consider to make all you member variables protected by default instead of private. 通常,您应该考虑使所有成员变量默认受保护而不是私有。

The reason most programmers choose private is that they don't think about others who want to derive from their class and most introductory C++ manuals teach this style, in the sense that all the examples use private. 大多数程序员选择私有的原因是他们不考虑其他想要从他们的课程派生出来的人,大多数介绍性的C ++手册教导这种风格,因为所有的例子都使用私有。

EDIT 编辑

Code duplication and memory allocation are undesired side-effects of using the pimp design pattern and cannot be avoided to my knowledge. 代码重复和内存分配是使用皮条客设计模式的不良副作用,据我所知是无法避免的。

If you need to have Bimpl inherit Aimpl and you want to expose a consistent interface to them through A and B, B would also need to inherit A. 如果你需要让Bimpl继承Aimpl并且你想通过A和B向它们公开一致的接口,那么B也需要继承A.

One thing you can do to simplify things in this scenario is to have B inherit from A and only change the contructor such that B::B(...) {} creates a Bimpl, and add dispatches for all methods of Bimpl that are not in Aimpl. 在这种情况下,你可以做的一件事就是让B继承自A并且只改变构造函数,使B :: B(...){}创建一个Bimpl,并为Bimpl的所有方法添加调度。不在Aimpl。

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

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