需要明确的是,我不是在问多重继承是好是坏。我从那场辩论的双方都听到了很多争论。
我想知道 C++ 中是否存在任何类型的设计问题或场景,其中多重继承要么是完成某事的唯一方法,要么至少是所有其他替代方案的最佳方式,以至于它不会考虑其他任何事情的意义。
显然,这个问题不适用于不支持多重继承的语言。
需要明确的是,我不是在问多重继承是好是坏。我从那场辩论的双方都听到了很多争论。
我想知道 C++ 中是否存在任何类型的设计问题或场景,其中多重继承要么是完成某事的唯一方法,要么至少是所有其他替代方案的最佳方式,以至于它不会考虑其他任何事情的意义。
显然,这个问题不适用于不支持多重继承的语言。
没有多重继承就无法进行基于策略的设计。因此,如果基于策略的设计是解决您的问题的最优雅的方式,那么这意味着您需要多重继承来解决您的问题,而不是所有其他选项。
如果不滥用多重继承(就像所有语言一样),多重继承会非常有用。
有一种情况,您会从一个类继承,并可能在 Java 中实现一个或两个接口。我认为这是您可以通过 C++ 中的多重继承来解决的问题。
C++ 流使用多重继承:istream
并且ostream
都是iostream
. 由于它们都继承自ios_base
,因此您拥有钻石。
这是唯一“合理”的解决方案,因为标准库的流部分采用与算法和集合相同的路线是不合理的。所以 ostream 的行为是多态的,而不是像 Iterator(*) 这样的“鸭子类型”接口。
一旦有了动态多态性,就需要多重继承来同时实现多个接口。
(*) 大概这是因为其他任何事情都是一团糟。您必须能够编写操作流的实际函数,而不是强迫用户到处都有模板。这是因为写入“某个流,直到运行时我才知道什么”是很常见的,但不想操作“某个集合,直到运行时我才知道什么”。
如果您需要继承行为,而不仅仅是契约,多重继承很有用。但是,正如其他语言所展示的那样,多重继承并不是解决该问题的唯一方法,其代价是使继承树更深。因此,您必须并且可能只使用多重继承的情况非常罕见。
我已经阅读了 Java 接口等等,以便更好地了解这个问题的答案。接口背后的想法是创建一个抽象类,作为另一个类的模板。这里的优点是模板可以在一个具体的类中组合。例如-
父类- FoodStore 子类- CoffeeShop 子类- Bakery
使用此继承树,FoodStore 可以是 Bakery 或 CoffeeShop,但不能同时是两者。但那我们怎么称呼星巴克呢?
更好的方法,IMO-
父类- FoodStore 接口- CoffeeShop 接口- Bakery
公共类 Starbucks 扩展 FoodStore 实现 CoffeeShop、Bakery { ... }
您必须了解一点 Java 才能理解这一点,但必须掌握它。接口是相当基本的,IMO。
作为进一步的思考,也许接口被设计为遵守“不要重复自己”。很明显,现在我提到它。
当您想要继承功能而不是角色时,例如boost::noncopyable
(支持此功能的其他语言(与 Java 和 C# 不同)将其称为mixin)。
正如其他答案所说:
使用纯虚拟基类作为“接口”,如在 Java 中(http://en.wikipedia.org/wiki/Interface_(Java)),这是所有 OO 语言中非常常见的 OO 模式,不仅仅是 Java
做基于警察的设计
但是也:
当您必须组合两个或多个第三方类层次结构时,每个层次结构都要求对象从层次结构自己的基类派生,那么缺少多重继承将使您的代码复杂而繁琐。
namespace Object_Database {
class Object {
public:
virtual void store() ;
virtual void fetch() ;
};
}
namespace Reflectives {
class Object {
public:
virtual std::vector<std::string> > membernames();
virtual std::vector<std::string> > methodnames();
};
}
第一个层次结构允许用户创建可以序列化到对象数据库和从对象数据库中序列化的对象,并要求所有这些对象都派生自 Object_Database::Object 类。第二个层次结构允许用户创建可以在运行时查询其成员名称的对象,并要求所有此类对象都从 Reflectives::Object 派生。
如果您需要可以同时执行这两种操作的对象,您只需要编写:
class ReflectivePickle :
public Object_Database::Object,
public Reflectives::Object {
// ...
};
其他解决方案是不合理的。
当基类是“接口类”时,我倾向于在 C++ 中使用多重继承,即所有方法都是纯虚拟的基类,没有实现[记住你仍然可以定义一个实现,但你必须显式调用它],并且没有数据成员。非常类似于 Java 或(据我所知)C# 中的“接口”。
要在 C++ 中使用多态性,不能使用组合,必须使用(公共)继承。
因此,如果 Bar 类(公开)继承自 Printable 和 Serializable,我可以将对象视为可打印对象、可序列化对象或 Bar 对象(使用指针或引用)。
使用组合,您无法做到这一点。
如果您想查看多继承的漂亮实现,请查看 Eiffel。他们通过特征重命名解决了菱形问题,比范围解析简单得多,它甚至支持直接重复继承,例如:
A继承B,B,B
当需要使用这种类型的继承时。
他们的内核库是开源的,如果您想查看示例,则广泛使用多重继承。