公共继承允许您对 IS-A 关系建模,派生类能够重用基类的代码。
这是一个普遍的误解。继承的重点是不能重用基类的代码,而是利用现有的代码来处理提供不同行为的基类。也就是说,至少在 OO 理论中是这样。
C++ 是一种强大的灵活语言,并且没有单一的 UML 到该语言的映射(这就是那些期望从 UML 生成代码的人会一次又一次失败的地方,UML 没有捕获一些细粒度的细节)。
泛化和实现都是通过 C++ 中的继承0实现的,尽管在其他语言中它们有所不同。例如,在 Java 中,泛化是通过继承(extends
)实现的,通过接口实现(implements
)实现。
组合这是has-a关系,通常通过类的值成员来实现。请注意,这不是唯一的实现1。
聚合可以通过指针2和引用成员来实现,具体取决于其他标准,包括两个对象的相对生命周期以及引用是否可以重置为不同的对象。
使用没有明确建模,而是发生了。使用关系可以出现在接口中(接受或返回不同类型对象的函数使用该类型)或实现(在其定义中使用不同类型的函数——实例化该类型的对象)一些目的)也使用类型。有些人认为这两个是use的不同变体,第一个比第二个更严格,因为它增加了从使用的类型到使用你的类型本身的外部代码的更紧密的耦合。
最后,使用模板还有其他多种选择。例如,语言中的和之间没有关系std::vector<>::iterator
,std::deque<>::iterator
但是它们在语言中建模了RandomAccessIterator的概念,而设计为与RandomAccessIterator一起使用的模板可以同时使用它们(例如,std::sort
)。std::sort
两者都是随机访问迭代器,关系是该概念的泛化,尽管代码中根本不存在(如果他们最终为语言添加了一些概念的味道)。
0)一般来说,我们只会谈论公共继承,但情况并非如此。从不同类型公开继承的类型显然是在泛化/*实现*另一种类型/接口,但这也可以通过私有继承发生。在教授 OO 时不常见的一件事是一个类有两个独立的接口。一方面,有一个公共接口,您类型的用户通过该接口与您的对象进行交互。另一边有一个虚拟的接口,这是您的类型与扩展您的行为的其他类型之间的合同。C++ 中的一个常见习惯用法是 NVI 非虚拟接口,它试图通过强制分离来利用这一点:没有公共虚拟函数意味着公共和虚拟接口是完全隔离的。同样,类型 T 可能没有与基类的公共 is-a关系,尽管在内部它可以将基类的引用或指针传递给其他子系统。对于那些子系统,类型 T是基础。可以忽略受保护的继承,因为它被认为是无用的,没有激励性的使用示例。
1)在某些情况下,由其他需求驱动,它可以用指针类型的成员来实现,只要它们在构造时分配并在封闭类型的销毁时释放。在某些情况下,继承被滥用来进行组合和执行大小优化(空基优化)。例如,astd::map
可以从比较器继承而不是持有比较器(使用 SFINAE 来检测比较器何时是函子)。如果比较器的类型没有非常频繁的状态,编译器可以将比较器和第一个成员std::map
放在同一个内存位置(即比较器不会占用任何空间)。
2)在一般意义上考虑术语指针,包括智能指针。