C ++如何使用共享的共同祖先处理多重继承?


13

我不是C ++的人,但是我不得不考虑这一点。为什么在C ++中可能有多重继承,但在C#中却没有?(我知道钻石的问题,但这不是我要问的)。C ++如何解决从多个基类继承的相同方法签名的歧义?为什么同一个设计没有合并到C#中?


3
为了完整起见,请问“钻石问题”是什么?
jcolebrand

1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritance看到钻石问题
13年

1
当然,我用谷歌搜索,但是我怎么知道那是Sandeep的意思呢?如果您要使用晦涩的名称来引用某些内容,则“直接使用共同祖先的多重继承”更为直接...
jcolebrand 2013年

1
@jcolebrand我对其进行了编辑以反映出我从问题中得到的内容。我认为他的意思是从上下文中引用维基百科上的钻石问题。下次删除友好的注释,并使用闪亮的新建议的编辑工具:)
Earlz 2013年

1
@Earlz仅在我理解参考文献时有效。否则,我将进行错误的编辑,那可真是个恶作剧。
jcolebrand 2013年

Answers:


24

为什么在C ++中可能有多重继承,但在C#中却没有?

我认为(没有硬性参考),他们希望在Java中限制该语言的表达能力,以使该语言更易于学习,因为使用多重继承的代码出于自身的利益而往往过于复杂。而且由于完全多重继承的实现要复杂得多,因此它也大大简化了虚拟机(多重继承与垃圾收集器的交互特别糟糕,因为它需要将指针保持在对象的中间(在基础的开头)。 )

在设计C#时,我认为他们看着Java,看到了完全多重继承的确并没有错失太多,因此也选择保持简单。

C ++如何解决从多个基类继承的相同方法签名的歧义?

事实并非如此。有一种语法可以从特定的基础显式地调用基类方法,但是无法覆盖仅一个虚拟方法,并且如果您不覆盖子类中的方法,则在不指定基础的情况下就无法调用它类。

为什么同一个设计没有合并到C#中?

没有什么可以合并的。


由于Giorgio在评论中提到了接口扩展方法,因此我将解释什么是mixin以及如何以各种语言实现它们。

Java和C#中的接口仅限于声明方法。但是必须在继承该接口的每个类中实现这些方法。但是,存在大量的接口,在其中提供一些方法的默认实现会很有用。常见示例是可比较的(使用伪语言):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

与完整类的区别在于,它不能包含任何数据成员。有几种实现方法。显然,多重继承是其中之一。但是多重继承的实现相当复杂。但这并不是真正需要的。取而代之的是,许多语言通过在一个由类实现的接口和方法实现的存储库中拆分混合来实现此目的,这些接口要么注入到类本身中,要么生成一个中间基类,然后将它们放置在该类中。这是在Ruby和D中实现的,将在Java 8中实现,并且可以使用奇怪的重复模板模式在C ++中手动实现。上面的CRTP格式如下:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

的使用方式如下:

class Concrete : public IComparable<Concrete> { ... };

这不需要像常规基类那样将任何东西声明为虚拟的,因此,如果在模板中使用该接口,则会打开有用的优化选项。请注意,在C ++中,它可能仍将作为第二父级继承,但是在不允许多重继承的语言中,它将插入到单个继承链中,因此更像

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

编译器实现可能会也可能不会避免虚拟分派。

在C#中选择了其他实现。在C#中,实现是完全独立的类的静态方法,如果给定名称的方法不存在,但定义了“扩展方法”,则编译器会适当地解释方法调用语法。这样做的优点是可以将扩展方法添加到已编译的类中,而缺点是不能覆盖此类方法(例如提供优化版本)。


可能要提到的是,使用接口扩展方法将多重继承引入Java 8。
乔治,2013年

@Giorgio:不,Java绝对不会引入多重继承。Mixins将会是非常不同的事情,尽管它涵盖了使用多重继承的许多剩余原因以及使用好奇重复模板模式(CRTP)的大多数原因,并且大多数情况下都像CRTP一样工作,而不是完全像多重继承那样。
Jan Hudec

我认为多重继承不需要将指针指向对象的中间。如果确实如此,那么多个接口继承也将需要它。
svick

@svick:不,不是。但是,替代方法的效率要低得多,因为它需要成员访问的虚拟调度。
Jan Hudec 2013年

+1比我的答案更完整。
内森·特雷施

2

答案是在命名空间冲突的情况下,它在C ++中无法正常工作。看到这个。为了避免名称空间冲突,您必须使用指针进行各种旋转。我在Visual Studio团队的MS部门工作,至少部分原因是他们开发了委派的原因是完全避免名称空间冲突。之前我曾说过,他们也认为接口是多重继承解决方案的一部分,但我误会了。接口实际上是很棒的,可以在C ++,FWIW中使用。

委托专门解决名称空间冲突:您可以委托5个类,所有5个类都将其方法作为第一类成员导出到您的作用域中。从外部看,这是多重继承。


1
我发现这是不太可能在C#中不包含MI的主要原因。此外,本文没有回答当您没有“命名空间冲突”时,MI为何能在C ++中工作的问题。
布朗

@DocBrown我曾在Visual Studio团队的MS上工作,我向您保证,至少部分原因是他们开发了委托和接口,从而完全避免了名称空间冲突。至于问题的质量,嗯。用你的票,其他人似乎认为这很有用。
内森·T·雷施

1
我无意拒绝您的回答,因为我认为这是正确的。但是您的答案假装MI在C ++中是完全不可用的,这就是他们没有在C#中引入它的原因。尽管我不太喜欢C ++中的MI,但我认为这并不是完全不可能的。
Doc Brown

1
这是原因之一,我并不是要暗示它永远不会起作用。当某项在计算中不确定时,我倾向于说它“坏了”,或者当我打算说“它像伏都教时,它可能不起作用,点燃蜡烛并祈祷”时,它说“不起作用”。:D
Nathan C.

1
命名空间冲突是不确定的吗?
布朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.