具有继承性的 Pimpl 习语

发布于 2024-07-12 01:47:58 字数 844 浏览 8 评论 0原文

我想将 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 中使用它。B
  3. 不应继承自 A。在 B 中创建 BImpl* pBImpl 成员,并在 B 中创建 foo() 和 bar(),这将使用pBImpl。
  4. 还有其他办法吗?

我应该选择什么?

I want to use pimpl idiom with inheritance.

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.

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*).
  2. Change pAImpl to be protected (or add a Get function), and use it in 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.
  4. Any other way?

What should I choose?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

溇涏 2024-07-19 01:47:58
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*/};
};
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*/};
};
把梦留给海 2024-07-19 01:47:58

我认为从纯粹面向对象理论的角度来看,最好的方法是不要让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 是可分配的,无论是偶然还是有意的。 这有点微妙地违反了里氏可替代性原则。 解决方案是:

  1. 构建后不要更改 pimpl 成员。 将它们声明为AImpl * const pimpl。 然后,派生构造函数可以传递适当的类型,并且派生类的其余部分可以放心地向下转型。 但是,您就不能进行非抛出交换、赋值或写时复制等操作,因为这些技术要求您可以更改 pimpl 成员。 然而,如果您有继承层次结构,您可能并不真正打算执行这些操作。

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

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?). 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. 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. 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. 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. It's a somewhat subtle violation of the Liskov substitutability principle. The solutions are to either:

  1. Don't change the pimpl members after construction. Declare them to be 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 e.g. do non-throwing swaps, assignments, or copy-on-write, because these techniques require that you can change the pimpl member. 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. If B wants to do something to A, then use A's public or protected interface. 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.

旧人 2024-07-19 01:47:58

我会做(1),因为A的私有对B来说无关紧要。

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

I would do (1) because A's privates are or no business for B.

Actually I would not pass it to A as you suggest, because A makes its own in A::A().
Calling pApimpl->whatever() from Bis also not appropriate (private means private).

眼泪淡了忧伤 2024-07-19 01:47:58

正如 stefan.ciobaca 所说,如果您确实希望 A 可扩展,您就会希望 pAImpl 受到保护。

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

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

  1. 您的替代方案 (3)。
  2. (3) 的变体,其中 BImpl 扩展 AImpl(继承 foo 的现有实现,而不是定义另一个实现),BImpl< /code> 定义了 bar,而 B 使用其私有的 BImpl* pBImpl 来访问两者。
  3. 委托,其中 B 持有指向 AImplBImpl 的私有指针,并转发 foo>bar 给适当的实施者。

As stefan.ciobaca said, if you really wanted A to be extendable, you'd want pAImpl to be protected.

However, your definition in B of void bar(){pAImpl->bar();}; seems odd, as bar is a method on BImpl and not AImpl.

There are at least three easy alternatives that would avoid that issue:

  1. Your alternative (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. Delegation, in which B holds private pointers to each of AImpl and BImpl and forwards each of foo and bar to the appropriate implementer.
◇流星雨 2024-07-19 01:47:58

正确的做法是(2)。

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

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

编辑

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

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

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

The correct way is to do (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.

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文