使用私有继承隐藏实现是个好主意吗?

Posted

技术标签:

【中文标题】使用私有继承隐藏实现是个好主意吗?【英文标题】:Is it a good idea to use private inheritance to hide implementation? 【发布时间】:2012-09-21 01:32:59 【问题描述】:

例如

// Implementation.
struct PrivatePoint 
  void SomePrivateMethod();

  double x;
  double y;


struct Point : private PrivatePoint 
  double DistanceTo(const Point& other) const;

这似乎类似于Pimpl idiom。这有两个我非常喜欢的优点:

    SomePrivateMethod 是可测试的。如果 SomePrivateMethod 在 Point 中被声明为私有,您将无法从测试中调用它。如果您在 Point 中将其声明为 public 或 protected,测试将能够调用它,但 Point 的普通用户也可以。 与在 Pimpl 惯用语中的操作相比,访问私有数据更容易读写,因为您不必通过指针,例如

.

Point::DistanceTo(const Point& other) 
  SomePrivateMethod();

  double dx = other.x - x;
  double dy = other.y - y;
  return sqrt(dx * dx + dy * dy);

对比

Point::DistanceTo(const Point& other) 
  ptr->SomePrivateMethod();

  double dx = other.ptr->x - ptr->x;
  double dy = other.ptr->y - ptr->y;
  return sqrt(dx * dx + dy * dy);

【问题讨论】:

【参考方案1】:

你的建议有一些缺点......

用户可以将自己耦合到“私有”类。

pimpl idiom 的主要目的是作为编译防火墙,允许在实现文件中指定私有成员,因此可以在不触及标题和需要/触发客户端重新编译的情况下更改私有成员,这与重新链接不同,后者是更快,如果更新是动态加载的库,甚至可能不需要涉及客户端应用程序的任何操作。您将失去这些好处。

SomePrivateMethod 是可测试的。如果 SomePrivateMethod 在 Point 中被声明为私有,您将无法从测试中调用它。如果您在 Point 中将其声明为 public 或 protected,测试将能够调用它,但 Point 的普通用户也可以调用它。

还有其他方便的选项:例如 - 您可以声明与测试代码的友谊,或使用预处理器构建暴露数据的测试模式。

【讨论】:

重新耦合:我认为这在实践中不会有问题,因为 PrivatePoint 明确标记为“请勿使用”。 re firewall: 有没有办法做Pimpl 易读易写?快速构建是好的,但可读的代码也是如此:/ @allyourcode:你对pimpl成语的读写有什么顾虑? @DavidRodríguez-dribeas 只是没有那么简单。看看我的问题中的两个例子。 Pimpl(通过设计)引入了一个不必要的(但可能是有益的)间接层;而不是直接访问数据,你必须通过一个指针。 @allyourcode: re "不要使用" - 一个试图快速完成某事的懒惰客户并不总是关心。关于 pImpl 的麻烦:指向的实现类不必仅限于数据成员 - 一些实现代码也可以直接放在那里,不需要继续使用指针。暴露的外部类确实需要使用指针(其他选项是可能的,但更不方便)。【参考方案2】:

私有继承与组合(和 pimpl)非常相似,但有一些缺点需要考虑:

    您需要使 PrivatePoint 定义在公共标头中可见。这引入了对 PrivatePoint 的编译时间依赖性,并且每次 PrivatePoint 更改时都需要 Point 的客户端重新编译。对于 pimpl,情况并非如此,只需像这样转发声明 PrivatePoint 就足够了: 结构 PrivatePoint;

    封装是一周。通过私有继承,扩展 Point 的客户端能够实现自己的 SomePrivateMethod 版本(C++ 允许覆盖私有虚拟方法)。在您的示例中,这不是问题,但如果 SomePrivateMethod 被声明为虚拟的,则会出现问题

如果您使用 pimpl,您还可以轻松地对 PrivatePoint 进行单元测试。

【讨论】:

感谢您提到私有虚拟成员函数可以被覆盖。这似乎是错误的,但话又说回来,我们在谈论 C++ :P。【参考方案3】:

不,因为无论如何你必须发布声明基类的头文件。私有继承保护您或您的用户免受对数据/方法的不必要访问。

【讨论】:

用户如何访问我不希望他们访问的数据?我的库永远不会实例化 PrivatePoint;它仅作为 Point 的基类存在。如果用户实例化 PrivatePoint,那么该库的未来版本将破坏他。但这是他自己的错,因为这是一个非常明显的错误(恕我直言)。即使他忽略了所有大写的 cmets 说 NEVER INSTANTIATE THIS CLASS,这个名字本身就应该很明显。 @allyourcode:假设人们不会使用界面中提供的类型是假设很多。历史证明,用户倾向于不这样做,询问微软和那些利用操作系统不可访问结构中的未记录字段并迫使微软为那些不仅不打算使用,甚至无法通过操作系统提供的接口 @allyourcode 然后告诉我你的情况和这个有什么区别: struct Point private: void SomePrivateMethod();双x;双 y; public: double DistanceTo(const Point& other); @Allyourcode:无论如何,实现细节仍然是隐藏的。正如我之前提到的,您必须向库的最终用户提供声明基类的头文件,否则用户甚至无法编译使用您的 Point 类的代码。如果您真的只想公开方法,那么您需要实现一些映射以将实际指针映射到用户可以看到的实例到您的内部分配的对象。你需要这种复杂性吗?您要保护什么样的专有技术?如何将铅变成金? ;) @Serge 关于您的第一条评论:拥有 PrivatePoint 及其成员函数 SomePrivateMethod 允许我在测试中调用 SomePrivateMethod。 Pimpl 也允许这样做,但它的缺点是使 Point 代码更加笨拙。

以上是关于使用私有继承隐藏实现是个好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章

扩展std :: vector是个好主意吗?

使用硬件性能计数器是个好主意吗

在现有 AspNetUser 表中手动创建列是个好主意吗?

使用 Nibs 进行国际化。这真的是个好主意吗?

对于 API 级别最低为 21 的项目放弃 appcompat 是个好主意吗?

使用 Spring AOP 记录是个好主意吗?