为什么要避免多重继承?

发布于 2024-07-11 06:43:42 字数 30 浏览 6 评论 0原文

使用多重继承是一个好概念还是我可以做其他事情?

Is it a good concept to use multiple inheritance or can I do other things instead?

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

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

发布评论

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

评论(15

木緿 2024-07-18 06:43:42

多重继承(缩写为 MI)闻起来,这意味着它通常是出于不良原因而进行的,并且会在维护者面前遭到反击。

总结

  1. 考虑功能的组合,而不是继承
  2. 警惕恐惧钻石
  3. 考虑使用多个接口而不是对象的继承
  4. 有时,多重继承是正确的选择。 如果是,那就使用它。
  5. 准备好在代码审查中捍卫您的多重继承架构

1. 也许是组合?

对于继承来说是如此,对于多重继承来说更是如此。

你的对象真的需要从另一个对象继承吗? Car 不需要继承 Engine 即可工作,也不需要继承 Wheel汽车有一个发动机和四个车轮

如果您使用多重继承而不是组合来解决这些问题,那么您就做错了。

2. 恐惧钻石

通常,你有一个类A,然后BC都继承自A 。 然后(不要问我为什么)有人决定 D 必须同时继承 BC

我在八年内两次遇到这种问题,很有趣,因为:

  1. 从一开始就犯了多大的错误(在这两种情况下, D 不应该继承自BC),因为这是糟糕的架构(事实上,C 根本不应该存在......
  2. )我们为此付出了代价,因为在 C++ 中,父类 A 在其孙类 D 中出现了两次,因此更新了一个父类 A::field 意味着要么更新它两次(通过 B::fieldC::field),要么让某些东西默默地出错并稍后崩溃(新的指针)在 B::field 中,并删除 C::field...)

在 C++ 中使用关键字 virtual 来限定继承可以避免上述的双重布局(如果不是的话)你想要什么,但无论如何,根据我的经验,你可能做错了什么......

在对象层次结构中,你应该尝试将层次结构保持为树(一个节点有一个父节点),而不是图形。

有关钻石的更多信息(编辑于 2017-05-03)

C++ 中的恐惧钻石的真正问题(假设设计合理 - 请审查您的代码!),您需要做出选择

  • A类是否需要在布局中存在两次,这意味着什么? 如果是,那么无论如何都要继承它两次。
  • 如果它只存在一次,则从虚拟继承。

这种选择是问题所固有的,在 C++ 中,与其他语言不同,您实际上可以做到这一点,而无需教条强制您在语言级别进行设计。

但与所有权力一样,权力伴随着责任:审查您的设计。

3. 接口

零个或一个具体类的多重继承,零个或多个接口通常是可以的,因为你不会遇到上面描述的恐惧钻石。 事实上,这就是 Java 中的工作方式。

通常,当 C 继承自 AB 时,您的意思是用户可以像使用 A 一样使用 C code>,和/或就好像它是 B

在 C++ 中,接口是一个抽象类,它具有:

  1. 其所有方法都声明为纯虚函数(后缀为 = 0) (删除了 2017-05-03)
  2. 没有成员变量

零到一个真实对象的多重继承以及零个或多个接口不被认为是“臭”的(至少没有那么臭)。

有关 C++ 抽象接口的更多信息(编辑 2017-05-03)

首先,NVI 模式可用于生成接口,因为真正的标准是没有状态(即没有成员变量,除了这个)。 您的抽象接口的目的是发布合同(“您可以这样称呼我,这样”),仅此而已。 仅具有抽象虚拟方法的限制应该是一种设计选择,而不是一种义务。

其次,在 C++ 中,从抽象接口虚拟继承是有意义的(即使有额外的成本/间接)。 如果不这样做,并且接口继承在层次结构中多次出现,那么就会产生歧义。

第三,面向对象固然很棒,但它并不是 C++ 中的唯一的真理TM。 使用正确的工具,并始终记住 C++ 中还有其他范例提供不同类型的解决方案。

4.你真的需要多重继承吗?

有时候是。

通常,您的 C 类继承自 AB,以及 AB > 是两个不相关的对象(即不在同一层次结构中、没有共同点、不同的概念等)。

例如,您可以拥有一个具有 X、Y、Z 坐标的节点系统,能够进行大量几何计算(可能是一个点、几何对象的一部分),并且每个节点都是一个自动化代理,能够与其他代理进行通信。

也许您已经可以访问两个库,每个库都有自己的名称空间(使用名称空间的另一个原因......但您使用名称空间,不是吗?),一个是 geo ,另一个是 < code>ai

因此,您拥有自己的 own::Node,它们均源自 ai::Agentgeo::Point

这是你应该问自己是否不应该使用组合的时刻。 如果 own::Node 确实既是 ai::Agent 又是 geo::Point,那么组合就不行。

然后,您将需要多重继承,让您的 own::Node 根据其他代理在 3D 空间中的位置与它们进行通信。

(您会注意到 ai::Agentgeo::Point 完全、完全、完全不相关......这大大降低了多重继承的危险)

其他情况(编辑 2017-05-03)

还有其他情况:

  • 使用(希望是私有的)继承作为实现细节,
  • 一些 C++ 习惯用法(如策略)可以使用多重继承(当每个部分需要通过 < code>this)
  • 来自 std::exception 的虚拟继承(异常是否需要虚拟继承?
  • 等。

有时可以使用组合,有时使用 MI 更好。 关键是:你有选择。 负责任地去做(并审查您的代码)。

5. 那么,我应该进行多重继承吗?

根据我的经验,大多数时候,不会。 MI 并不是一个正确的工具,即使它看起来有效,因为它可以被懒惰的人使用,将功能堆积在一起而没有意识到后果(例如使 Car 既是 Engine和一个)。

但有时,是的。 到那时,没有什么比 MI 更有效的了。

但因为 MI 很臭,所以准备好在代码审查中捍卫你的架构(捍卫它是一件好事,因为如果你无法捍卫它,那么你就不应该这样做)。

Multiple inheritance (abbreviated as MI) smells, which means that usually, it was done for bad reasons, and it will blow back in the face of the maintainer.

Summary

  1. Consider composition of features, instead of inheritance
  2. Be wary of the Diamond of Dread
  3. Consider inheritance of multiple interfaces instead of objects
  4. Sometimes, Multiple Inheritance is the right thing. If it is, then use it.
  5. Be prepared to defend your multiple-inherited architecture in code reviews

1. Perhaps composition?

This is true for inheritance, and so, it's even more true for multiple inheritance.

Does your object really need to inherit from another? A Car does not need to inherit from an Engine to work, nor from a Wheel. A Car has an Engine and four Wheel.

If you use multiple inheritance to resolve these problems instead of composition, then you've done something wrong.

2. The Diamond of Dread

Usually, you have a class A, then B and C both inherit from A. And (don't ask me why) someone then decides that D must inherit both from B and C.

I've encountered this kind of problem twice in eight years, and it is amusing to see because of:

  1. How much of a mistake it was from the beginning (In both cases, D should not have inherited from both B and C), because this was bad architecture (in fact, C should not have existed at all...)
  2. How much maintainers were paying for that, because in C++, the parent class A was present twice in its grandchild class D, and thus, updating one parent field A::field meant either updating it twice (through B::field and C::field), or having something go silently wrong and crash, later (new a pointer in B::field, and delete C::field...)

Using the keyword virtual in C++ to qualify the inheritance avoids the double layout described above if this is not what you want, but anyway, in my experience, you're probably doing something wrong...

In Object hierarchy, you should try to keep the hierarchy as a Tree (a node has ONE parent), not as a graph.

More about the Diamond (edit 2017-05-03)

The real problem with the Diamond of Dread in C++ (assuming the design is sound - have your code reviewed!), is that you need to make a choice:

  • Is it desirable for the class A to exist twice in your layout, and what does it mean? If yes, then by all means inherit from it twice.
  • if it should exist only once, then inherit from it virtually.

This choice is inherent to the problem, and in C++, unlike other languages, you can actually do it without dogma forcing your design at language level.

But like all powers, with that power comes responsibility: Have your design reviewed.

3. Interfaces

Multiple inheritance of zero or one concrete classes, and zero or more interfaces is usually Okay, because you won't encounter the Diamond of Dread described above. In fact, this is how things are done in Java.

Usually, what you mean when C inherits from A and B is that users can use C as if it was an A, and/or as if it was a B.

In C++, an interface is an abstract class which has:

  1. all its method declared pure virtual (suffixed by = 0) (removed the 2017-05-03)
  2. no member variables

The Multiple inheritance of zero to one real object, and zero or more interfaces is not considered "smelly" (at least, not as much).

More about the C++ Abstract Interface (edit 2017-05-03)

First, the NVI pattern can be used to produce an interface, because the real criteria is to have no state (i.e. no member variables, except this). Your abstract interface's point is to publish a contract ("you can call me this way, and this way"), nothing more, nothing less. The limitation of having only abstract virtual methods should be a design choice, not an obligation.

Second, in C++, it makes sense to inherit virtually from abstract interfaces, (even with the additional cost/indirection). If you don't, and the interface inheritance appears multiple times in your hierarchy, then you'll have ambiguities.

Third, object orientation is great, but it is not The Only Truth Out ThereTM in C++. Use the right tools, and always remember you have other paradigms in C++ offering different kinds of solutions.

4. Do you really need Multiple Inheritance?

Sometimes, yes.

Usually, your C class is inheriting from A and B, and A and B are two unrelated objects (i.e. not in the same hierarchy, nothing in common, different concepts, etc.).

For example, you could have a system of Nodes with X,Y,Z coordinates, able to do a lot of geometric calculations (perhaps a point, part of geometric objects) and each Node is an Automated Agent, able to communicate with other agents.

Perhaps you already have access to two libraries, each with its own namespace (another reason to use namespaces... But you use namespaces, don't you?), one being geo and the other being ai

So you have your own own::Node derive both from ai::Agent and geo::Point.

This is the moment when you should ask yourself if you should not use composition instead. If own::Node is really really both a ai::Agent and a geo::Point, then composition will not do.

Then you'll need multiple inheritance, having your own::Node communicate with other agents according to their position in a 3D space.

(You'll note that ai::Agent and geo::Point are completely, totally, fully UNRELATED... This drastically reduces the danger of multiple inheritance)

Other cases (edit 2017-05-03)

There are other cases:

  • using (hopefully private) inheritance as implementation detail
  • some C++ idioms like policies could use multiple inheritance (when each part needs to communicate with the others through this)
  • the virtual inheritance from std::exception (Is Virtual Inheritance necessary for Exceptions?)
  • etc.

Sometimes you can use composition, and sometimes MI is better. The point is: You have a choice. Do it responsibly (and have your code reviewed).

5. So, should I do Multiple Inheritance?

Most of the time, in my experience, no. MI is not the right tool, even if it seems to work, because it can be used by the lazy to pile features together without realizing the consequences (like making a Car both an Engine and a Wheel).

But sometimes, yes. And at that time, nothing will work better than MI.

But because MI is smelly, be prepared to defend your architecture in code reviews (and defending it is a good thing, because if you're not able to defend it, then you should not do it).

栩栩如生 2024-07-18 06:43:42

来自对 Bjarne Stroustrup 的采访

人们说你不需要多重继承是非常正确的,因为你可以用多重继承做的任何事情你也可以用单继承做。 你只需使用我提到的委托技巧即可。 此外,您根本不需要任何继承,因为您使用单一继承执行的任何操作也可以通过类转发而无需继承执行。 实际上,您也不需要任何类,因为您可以使用指针和数据结构来完成这一切。 但你为什么要这么做呢? 什么时候方便使用语言设施? 您什么时候更喜欢解决方法? 我见过多重继承有用的情况,甚至见过相当复杂的多重继承有用的情况。 一般来说,我更喜欢使用该语言提供的工具来解决问题

From an interview with Bjarne Stroustrup:

People quite correctly say that you don't need multiple inheritance, because anything you can do with multiple inheritance you can also do with single inheritance. You just use the delegation trick I mentioned. Furthermore, you don't need any inheritance at all, because anything you do with single inheritance you can also do without inheritance by forwarding through a class. Actually, you don't need any classes either, because you can do it all with pointers and data structures. But why would you want to do that? When is it convenient to use the language facilities? When would you prefer a workaround? I've seen cases where multiple inheritance is useful, and I've even seen cases where quite complicated multiple inheritance is useful. Generally, I prefer to use the facilities offered by the language to doing workarounds

呆° 2024-07-18 06:43:42

没有理由避免它,并且它在某些情况下非常有用。 但您需要意识到潜在的问题。

最大的一颗是死亡钻石:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

你现在在孩子体内拥有祖父母的两个“副本”。

C++ 已经考虑到了这一点,并允许您进行虚拟继承来解决这些问题。

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

始终检查您的设计,确保您没有使用继承来节省数据重用。 如果您可以用组合来表示相同的事物(通常可以),这是一种更好的方法。

There's no reason to avoid it and it can be very useful in situations. You need to be aware of the potential issues though.

The biggest one being the diamond of death:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

You now have two "copies" of GrandParent within Child.

C++ has thought of this though and lets you do virtual inheritence to get around the issues.

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

Always review your design, ensure you are not using inheritance to save on data reuse. If you can represent the same thing with composition (and typically you can) this is a far better approach.

却一份温柔 2024-07-18 06:43:42

请参阅 w:多重继承

多重继承已收到
批评,因此,不是
以多种语言实现。
批评包括:

  • 复杂性增加
  • 语义歧义通常被概括为钻石
    问题
  • 无法从单个对象显式继承多次
  • 继承顺序改变类语义。

语言中的多重继承
C++/Java 风格的构造函数
加剧了继承问题
构造函数和构造函数链,
从而创建维护和
这些方面的可扩展性问题
语言。 继承中的对象
关系差异很大
施工方法很难
在构造函数下实现
链接范例。

解决这个问题的现代方法是使用接口(纯抽象类),如 COM 和 Java 接口。

我可以做其他事情来代替这个吗?

是的你可以。 我要从 GoF 那里窃取信息。

  • 针对接口而不是实现进行编程
  • 优先选择组合而不是继承

See w:Multiple Inheritance.

Multiple inheritance has received
criticism and as such, is not
implemented in many languages.
Criticisms includes:

  • Increased complexity
  • Semantic ambiguity often summarized as the diamond
    problem
    .
  • Not being able to explicitly inherit multiple times from a single
    class
  • Order of inheritance changing class semantics.

Multiple inheritance in languages with
C++/Java style constructors
exacerbates the inheritance problem of
constructors and constructor chaining,
thereby creating maintenance and
extensibility problems in these
languages. Objects in inheritance
relationships with greatly varying
construction methods are hard to
implement under the constructor
chaining paradigm.

Modern way of resolving this to use interface (pure abstract class) like COM and Java interface.

I can do other things in place of this?

Yes, you can. I am going to steal from GoF.

  • Program to an Interface, not an Implementation
  • Prefer composition over inheritance
梦冥 2024-07-18 06:43:42

公共继承是一种 IS-A 关系,有时一个类将是几个不同类的类型,有时反映这一点很重要。

“Mixins”有时也很有用。 它们通常是小类,通常不继承任何东西,提供有用的功能。

只要继承层次结构相当浅(几乎总是如此)并且管理良好,您就不太可能获得可怕的钻石继承。 菱形并不是所有使用多重继承的语言都会遇到的问题,但 C++ 对它的处理常常很尴尬,有时甚至令人费解。

虽然我遇到过多重继承非常方便的情况,但实际上这种情况相当罕见。 这可能是因为当我并不真正需要多重继承时,我更喜欢使用其他设计方法。 我确实更喜欢避免令人困惑的语言构造,并且很容易构建继承案例,您必须很好地阅读手册才能弄清楚发生了什么。

Public inheritance is an IS-A relationship, and sometimes a class will be an type of several different classes, and sometimes it's important to reflect this.

"Mixins" are also sometimes useful. They are generally small classes, usually not inheriting from anything, providing useful functionality.

As long as the inheritance hierarchy is fairly shallow (as it should almost always be), and well managed, you're unlikely to get the dreaded diamond inheritance. The diamond isn't a problem with all languages that use multiple inheritance, but C++'s treatment of it is frequently awkward and sometimes puzzling.

While I've run into cases where multiple inheritance is very handy, they're actually fairly rare. This is likely because I prefer to use other design methods when I don't really need multiple inheritance. I do prefer to avoid confusing language constructs, and it's easy to construct inheritance cases where you have to read the manual really well to figure out what's going on.

小女人ら 2024-07-18 06:43:42

您不应该“避免”多重继承,但您应该意识到可能出现的问题,例如“钻石问题”( http://en.wikipedia.org/wiki/Diamond_problem)并谨慎对待赋予您的权力,就像对待所有权力一样。

You shouldn't "avoid" multiple inheritance but you should be aware of problems that can arise such as the 'diamond problem' ( http://en.wikipedia.org/wiki/Diamond_problem ) and treat the power given to you with care, as you should with all powers.

乖乖兔^ω^ 2024-07-18 06:43:42

你应该谨慎使用它,在某些情况下,比如钻石问题,事情可能会顺利进行复杂的。

替代文本
(来源:learncpp.com

You should use it carefully, there are some cases, like the Diamond Problem, when things can go complicated.

alt text
(source: learncpp.com)

很糊涂小朋友 2024-07-18 06:43:42

冒着变得有点抽象的风险,我发现在范畴论框架内思考继承很有启发性。

如果我们认为所有类和它们之间的箭头表示继承关系,那么类似这样的事情

A --> B

意味着类 B 派生自类 A。 请注意,假设

A --> B, B --> C

我们说 C 派生自 B,而 B 派生自 A,因此 C 也可以说是派生自 A,因此

A --> C

此外,我们说对于每个类 A 来说,简单地说 A 派生自 A,因此我们的继承模型满足了类别的定义。 在更传统的语言中,我们有一个类别Class,其中包含所有类的对象和态射的继承关系。

这是一些设置,但接下来让我们看一下我们的末日钻石:

C --> D
^     ^
|     |
A --> B

这是一个看起来很阴暗的图表,但它就可以了。 因此,D 继承自所有 ABC。 此外,更接近解决OP的问题,D还继承自A的任何超类。 我们可以画一个图

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

现在,与死亡钻石相关的问题是当CB共享一些属性/方法名称并且事情变得不明确时; 但是,如果我们将任何共享行为移至 A 中,那么歧义就会消失。

用分类术语来说,我们希望 ABC 满足以下条件:BC继承自Q,然后A可以重写为Q的子类。 这使得 A 称为 pushout

D 上还有一个对称结构,称为 pullback。 这本质上是您可以构造的最通用的有用类,它继承自 BC。 也就是说,如果您有任何其他类 R 乘法继承自 BC,则 D 是一个类其中R可以重写为D的子类。

确保您的菱形尖端是回调和推出,这为我们提供了一种很好的方法来一般处理否则可能出现的名称冲突或维护问题。

注意Paercebal答案启发了这一点,因为考虑到我们在所有可能类别的完整类别 Class 中工作,上述模型暗示了他的警告。

我想把他的论点概括为一些东西,以表明复杂的多重继承关系可以既强大又没有问题。

TL;DR 将程序中的继承关系视为形成一个类别。 然后,您可以通过使多重继承类推出并对称地创建一个公共父类(即回调)来避免末日钻石问题。

At the risk of getting a bit abstract, I find it illuminating to think about inheritance within the frame of category theory.

If we think of all our classes and arrows between them denoting inheritance relations, then something like this

A --> B

means that class B derives from class A. Note that, given

A --> B, B --> C

we say C derives from B which derives from A, so C is also said to derive from A, thus

A --> C

Furthermore, we say that for every class A that trivially A derives from A, thus our inheritance model fulfills the definition of a category. In more traditional language, we have a category Class with objects all classes and morphisms the inheritance relations.

That's a bit of setup, but with that let's take a look at our Diamond of Doom:

C --> D
^     ^
|     |
A --> B

It's a shady looking diagram, but it'll do. So D inherits from all of A, B, and C. Furthermore, and getting closer to addressing OP's question, D also inherits from any superclass of A. We can draw a diagram

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

Now, problems associated with the Diamond of Death here are when C and B share some property/method names and things get ambiguous; however, if we move any shared behavior into A then the ambiguity disappears.

Put in categorical terms, we want A, B and C to be such that if B and C inherit from Q then A can be rewritten as as subclass of Q. This makes A something called a pushout.

There is also a symmetric construction on D called a pullback. This is essentially the most general useful class you can construct which inherits from both B and C. That is, if you have any other class R multiply inheriting from B and C, then D is a class where R can be rewritten as as subclass of D.

Making sure your tips of the diamond are pullbacks and pushouts gives us a nice way to generically handle name-clashing or maintenance issues which might arise otherwise.

Note Paercebal's answer inspired this as his admonitions are implied by the above model given that we work in the full category Class of all possible classes.

I wanted to generalize his argument to something which shows how complicated multiple inheritance relationships can be both powerful and non-problematic.

TL;DR Think of the inheritance relationships in your program as forming a category. Then you can avoid Diamond of Doom problems by making multiply-inherited classes pushouts and symmetrically, making a common parent class which is a pullback.

猥琐帝 2024-07-18 06:43:42

我们使用埃菲尔铁塔。 我们有优秀的MI。 不用担心。 没有问题。 易于管理。 有时不使用 MI。 然而,它比人们意识到的更有用,因为他们:A)使用一种不能很好地管理它的危险语言 - 或 - B)对他们多年来围绕 MI 的工作感到满意 - 或 - C)其他原因(太多了,无法一一列出,我很确定——请参阅上面的答案)。

对于我们来说,使用 Eiffel、MI 和其他任何东西一样自然,也是工具箱中的另一个优秀工具。 坦率地说,我们并不担心没有其他人使用 Eiffel。 不用担心。 我们对我们所拥有的感到满意,并邀请您来看看。

当您查看时:请特别注意 Void-safety 和消除 Null 指针解除引用。 当我们都在围绕 MI 跳舞时,您的指针却迷失了! :-)

We use Eiffel. We have excellent MI. No worries. No issues. Easily managed. There are times to NOT use MI. However, it useful more than people realize because they are: A) in a dangerous language that does not manage it well -OR- B) satisfied with how they've worked around MI for years and years -OR- C) other reasons (too numerous to list I am quite sure--see answers above).

For us, using Eiffel, MI is as natural as anything else and another fine tool in the toolbox. Frankly, we're quite unconcerned that no one else is using Eiffel. No worries. We are happy with what we have and invite you to have a look.

While you're looking: Take special note of Void-safety and the eradication of Null pointer dereferencing. While we're all dancing around MI, your pointers are getting lost! :-)

む无字情书 2024-07-18 06:43:42

每种编程语言对面向对象编程的处理方式都略有不同,各有利弊。 C++ 的版本完全强调性能,但也有一个缺点,即很容易编写无效代码,这对于多重继承来说也是如此。 因此,程序员往往会远离此功能。

其他人已经解决了多重继承有什么不好的问题。 但我们看到不少评论或多或少暗示避免它的原因是因为它不安全。 嗯,是的,也不是。

正如 C++ 中的情况一样,如果您遵循基本准则,您就可以安全地使用它,而不必不断“小心谨慎”。 关键思想是区分一种特殊的类定义,称为“混合”; 如果类的所有成员函数都是虚拟的(或纯虚拟的),则该类是混合类。 然后,您可以继承单个主类和任意数量的“混合”,但您应该使用关键字“virtual”继承混合。 例如,

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

我的建议是,如果您打算使用一个类作为混合类,您还可以采用命名约定,以便任何阅读代码的人都可以轻松了解发生了什么以及发生了什么。 验证您是否遵守基本指南的规则。 而且您会发现,如果您的混入也具有默认构造函数,那么它的工作效果会更好,这只是因为虚拟基类的工作方式。 并记住将所有析构函数也设为虚拟。

请注意,我在这里使用的“mix-in”一词与参数化模板类不同(请参阅此链接有一个很好的解释),但我认为这是对术语的合理使用。

现在我不想给人留下这样的印象:这是安全使用多重继承的唯一方法。 这只是一种相当容易检查的方法。

Every programming language has a slightly different treatment of object-oriented programming with pros and cons. C++'s version places the emphasis squarely on performance and has the accompanying downside that it is disturbingly easy to write invalid code - and this is true of multiple inheritance. As a consequence there is a tendency to steer programmers away from this feature.

Other people have addressed the question of what multiple inheritance isn't good for. But we have seen quite a few comments that more-or-less imply that the reason to avoid it is because it's not safe. Well, yes and no.

As is often true in C++, if you follow a basic guideline you can use it safely without having to "look over your shoulder" constantly. The key idea is that you distinguish a special kind of class definition called a "mix-in"; class is a mix-in if all its member functions are virtual (or pure virtual). Then you are allowed to inherit from a single main class and as many "mix-ins" as you like - but you should inherit mixins with the keyword "virtual". e.g.

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

My suggestion is that if you intend to use a class as a mix-in class you also adopt a naming convention to make it easy for anyone reading the code to see what's happening & to verify you're playing by the rules of the basic guideline. And you'll find it works much better if your mix-ins have default constructors too, just because of the way virtual base classes work. And remember to make all the destructors virtual too.

Note that my use of the word "mix-in" here isn't the same as the parameterised template class (see this link for a good explanation) but I think it is a fair use of the terminology.

Now I don't want to give the impression that this is the only way to use multiple inheritance safely. It's just one way that is fairly easy to check.

高跟鞋的旋律 2024-07-18 06:43:42

继承的使用和滥用。

这篇文章很好地解释了继承,而且它危险。

Uses and Abuses of Inheritance.

The article does a great job of explaining inheritance, and it's dangers.

删除→记忆 2024-07-18 06:43:42

除了菱形模式之外,多重继承往往会使对象模型更难以理解,从而增加维护成本。

构图本质上很容易理解、理解和解释。 编写代码可能会很乏味,但是一个好的 IDE(我使用 Visual Studio 已经有几年了,但 Java IDE 肯定都有很棒的组合快捷自动化工具)应该可以帮助您克服这个障碍。

此外,在维护方面,“钻石问题”也出现在非文字继承实例中。 例如,如果您有 A 和 B,并且您的 C 类扩展了它们,并且 A 有一个用于制作橙汁的“makeJuice”方法,您将其扩展为用酸橙汁制作橙汁:当设计者为 ' B' 添加了一个生成电流的 'makeJuice' 方法? “A”和“B”可能现在是兼容的“父母”,但这并不意味着他们永远都是这样!

总体而言,避免继承(尤其是多重继承)的格言是合理的。 正如所有格言一样,也有例外,但您需要确保有一个闪烁的绿色霓虹灯符号指向您编码的任何异常(并训练您的大脑,以便每当您看到此类继承树时,您都会在自己的闪烁的绿色霓虹灯中绘制标志),并且你时不时地检查一下以确保这一切都有意义。

Beyond the diamond pattern, multiple inheritance tends to make the object model harder to understand, which in turn increases maintenance costs.

Composition is intrinsically easy to understand, comprehend, and explain. It can get tedious to write code for, but a good IDE (it's been a few years since I've worked with Visual Studio, but certainly the Java IDEs all have great composition shortcut automating tools) should get you over that hurdle.

Also, in terms of maintenance, the "diamond problem" comes up in non-literal inheritance instances as well. For instance, if you have A and B and your class C extends them both, and A has a 'makeJuice' method which makes orange juice and you extend that to make orange juice with a twist of lime: what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current? 'A' and 'B' may be compatible "parents" right now, but that doesn't mean they will always be so!

Overall, the maxim of tending to avoid inheritance, and especially multiple inheritance, is sound. As all maxims, there are exceptions, but you need to make sure that there is a flashing green neon sign pointing at any exceptions you code (and train your brain so that any time you see such inheritance trees you draw in your own flashing green neon sign), and that you check to make sure it all makes sense every once in a while.

吃→可爱长大的 2024-07-18 06:43:42

具体对象的 MI 的关键问题是,很少有一个对象可以合理地“成为 A 且成为 B”,因此从逻辑上讲,它很少是正确的解决方案。 更常见的是,您有一个对象 C,它遵循“C 可以充当 A 或 B”,这可以通过接口继承和接口继承来实现。 作品。 但请不要误会——多个接口的继承仍然是 MI,只是它的一个子集。

特别是对于 C++,该功能的主要弱点不是多重继承的实际存在,而是它允许的一些构造几乎总是格式错误的。 例如,继承同一对象的多个副本,如:

class B : public A, public A {};

根据定义,格式错误。 翻译成英文就是“B is an A and an A”。 因此,即使在人类语言中也存在严重的歧义。 您的意思是“B 有 2 个 A”还是“B 是一个 A”? 允许这样的病态代码,更糟糕的是让它成为一个使用示例,在为后续语言中保留该功能提供理由时,C++ 并没有得到任何好处。

The key issue with MI of concrete objects is that rarely do you have an object that legitimately should "Be an A AND be a B", so it is rarely the correct solution on logical grounds. Far more often, you have an object C that obeys "C can act as an A or a B", which you can achieve via interface inheritance & composition. But make no mistake- inheritance of multiple interfaces is still MI, just a subset of it.

For C++ in particular, the key weakness of the feature isn't the actual EXISTENCE of Multiple Inheritance, but some constructs it allows that are almost always malformed. For example, inheriting multiple copies of the same object like:

class B : public A, public A {};

is malformed BY DEFINITION. Translated into English this is "B is an A and an A". So, even in human language there's a severe ambiguity. Did you mean "B has 2 As" or just "B is an A"?. Allowing such pathological code, and worse making it a usage example, did C++ no favors when it came to making a case for keeping the feature in successor languages.

把时间冻结 2024-07-18 06:43:42

每个涉及的类需要 4/8 字节。
(每个类一个 this 指针)。

这可能永远不会成为一个问题,但如果有一天你有一个被实例化数十亿次的微观数据结构,那就会是一个问题。

it takes 4/8 bytes per class involved.
(One this pointer per class).

This might never be a concern, but if one day you have a micro data structure which is instanced billions of time it will be.

匿名。 2024-07-18 06:43:42

您可以优先使用组合而不是继承。

总体感觉构图比较好,讨论的也很好。

You can use composition in preference to inheritance.

The general feeling is that composition is better, and it's very well discussed.

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