继承基础

继承 — 基础知识

继承对 C++ 重要吗?

有的。

一些倡导面向对象(OO)编程的人认为,继承是区分抽象数据类型(ADT)编程和面向对象编程的关键。

那些认为面向对象编程在哲学上不健全的人,仍然发现继承在以下方面很有用:在通过继承混合类来构造类时,利用空基类优化,这些混合类将数据或行为“捐赠”给结果类;或者在模板元编程中促进标签分发。

我何时会使用继承?

如果你遵循面向对象范式,将其用作规范化工具。

人类在两个维度上抽象事物:部分-和-种类-。一辆福特金牛座是汽车的一种,一辆福特金牛座有一个发动机、轮胎等等。“部分-的”层次结构自抽象数据类型(ADT)风格变得相关以来就一直是软件的一部分;继承增加了分解的“另一个”主要维度。

如果你不遵循面向对象范式,使用继承来从“捐赠”类中混入行为和数据,并帮助进行标签分发。

在 C++ 中如何表达继承?

通过 : public 语法

class Car : public Vehicle {
public:
  // ...
};

我们用几种方式来表达上述关系:

  • CarVehicle 的“一种”
  • Car “派生自” Vehicle
  • CarVehicle 的“特化”
  • CarVehicle 的“子类”
  • CarVehicle 的“派生类”
  • VehicleCar 的“基类”
  • VehicleCar 的“超类”(这在 C++ 社区中不那么常见)

(注意:此 FAQ 涉及 public 继承;privateprotected 继承是不同的。)

将派生类指针转换为其基类指针是否可以?

是的。

派生类的对象是基类的一种。因此,从派生类指针到基类指针的转换是完全安全的,并且一直都在发生。例如,如果我指向一辆汽车,我实际上是指向一辆交通工具,所以将 Car* 转换为 Vehicle* 是完全安全和正常的。

void f(Vehicle* v);
void g(Car* c) { f(c); }  // Perfectly safe; no cast

(注意:此 FAQ 涉及 public 继承;privateprotected 继承是不同的。)

publicprivateprotected 有什么区别?

  • 在类的 private 部分声明的成员(数据成员或成员函数)只能由该类的成员函数和友元访问。
  • 在类的 protected 部分声明的成员(数据成员或成员函数)只能由该类的成员函数和友元访问,以及由派生类的成员函数和友元访问。
  • 在类的 public 部分声明的成员(数据成员或成员函数)可以被任何人访问。

为什么我的派生类不能访问基类的 private 内容?

为了保护你免受基类未来变更的影响。

派生类无法访问基类的 private 成员。这有效地“隔离”了派生类,使其不受基类 private 成员任何更改的影响。

当我更改基类的内部部分时,如何保护派生类不被破坏?

一个类为两组不同的客户端提供了两个不同的接口:

  • 它有一个服务于不相关类的 public 接口。
  • 它有一个服务于派生类的 protected 接口。

除非您期望所有派生类都由您的团队构建,否则您应该将基类的数据成员声明为 private,并使用 protected inline 访问函数,派生类将通过这些函数访问基类中的 private 数据。这样,private 数据声明可以更改,但派生类的代码不会被破坏(除非您更改 protected 访问函数)。

有人告诉我永远不要使用受保护的数据,而应该总是使用私有数据和受保护的访问函数。这是一个好规则吗?

不。

每当有人对你说:“你应该_总是_把数据设为私有”,停下来——这是一个“总是”或“从不”的规则,我称这些规则为“一刀切”的规则。现实世界没有那么简单。

我是这样说的:如果我期望有派生类,我应该问这个问题:谁将创建它们?如果创建它们的人在你的团队之外,或者如果有_大量_的派生类,那么只有在这种情况下,才值得创建一个受保护的接口并使用私有数据。如果我期望派生类由我自己的团队创建,并且数量合理,那就不值得麻烦了:使用受保护的数据。并且抬起头来,不要感到羞愧:这是_正确的事情_!

受保护访问函数的好处是,当您更改数据时,您的派生类被破坏的频率不会像数据受保护时那样高。这样说吧:如果您认为您的用户在您的团队之外,那么您应该做的不仅仅是为您的私有数据提供获取/设置方法。您实际上应该创建另一个接口。您有一个面向一组用户的公共接口,以及一个面向另一组用户的受保护接口。但它们都需要一个精心设计的接口——为稳定性、可用性、性能等而设计。归根结底,将数据私有化(包括提供一个一致且尽可能不透明的接口)的真正好处是,当您更改数据结构时,避免破坏您的派生类。

但是,如果派生类是由您自己的团队创建的,并且数量合理地少,那么这根本不值得:使用受保护的数据。一些纯粹主义者(翻译:那些从未踏足现实世界的人,那些一生都生活在象牙塔里的人,那些不理解“客户”、“日程”、“截止日期”或“投资回报率”等词语的人)认为_一切_都应该可重用,_一切_都应该有一个干净、易用的接口。这类人很危险:他们经常使您的项目延迟,因为他们把一切都看得同样重要。他们基本上是在说:“我们有100项任务,我已经仔细地给它们排了优先级:它们都是优先级1。”他们让优先级的概念变得毫无意义。

你根本没有足够的时间让_每个人_的生活都轻松,所以你所能做的最好就是让世界上的一部分人的生活轻松。确定优先级。选择最重要的人,并花时间为他们构建稳定的接口。你可能不喜欢这样,但不是每个人都生而平等;有些人确实_比其他人更重要。我们对那些重要的人有一个称呼。我们称他们为“客户”。

好的,那么我到底该如何决定是否构建一个“protected 接口”?

三个关键词:投资回报率(ROI)、投资回报率(ROI)和投资回报率(ROI)。

你构建的每个接口都有成本和收益。你构建的每个可重用组件都有成本和收益。每个测试用例,每个结构清晰的东西,任何形式的投资。如果没有积极的投资回报,你_永远不应该_在_任何_事情上投入_任何_时间或_任何_金钱。如果它的成本高于它能为你公司节省的,那就不要做!

并不是每个人都同意我的观点;他们有犯错的权利。例如,那些与现实世界相距甚远的人表现得好像_每项_投资都是好的。毕竟,他们认为,如果你等得足够久,也许总有一天会为某人节省一些时间。也许吧。我们希望如此。

这种推理方式是不专业和不负责任的。你没有无限的时间,所以要明智地投资。当然,如果你住在象牙塔里,你不必担心那些烦人的“日程”或“客户”。但在现实世界中,你必须在日程内工作,因此你必须只在能获得良好回报的地方投入时间。

回到最初的问题:何时应该投入时间构建一个受保护的接口?答案:当您获得良好的投资回报时。如果它将花费您一小时,请确保它为某人节省了超过一小时,并且确保节省不是“海市蜃楼”。如果您能_在_当前项目_内_节省一小时,那简直是显而易见的:去做吧。如果它将在未来的某个项目上节省一小时,也许我们希望如此,那么就不要做。如果介于两者之间,您的答案将取决于您的公司如何权衡未来与现在。

重点很简单:不要做任何可能损害您日程的事情。(或者如果您做了,请确保您永远不要与我共事;我会让您付出代价。)投资是好的,_如果_该投资有回报。不要天真幼稚;成熟起来,认识到有些投资是糟糕的,因为它们总的来说成本高于回报。