此代码无效:

struct Agent {};
struct DoubleAgent : public Agent, public Agent {};

因为:
> g++ -c DoubleAgent.cpp
DoubleAgent.cpp:2:43: error: duplicate base type ‘Agent’ invalid
 struct DoubleAgent : public Agent, public Agent {};
                                           ^

为什么?

我不认为这违反了继承的概念。如果一个类与基类可以具有 is- 关系,那么它是否应该与基类具有 is-of- two-of- 关系?特别考虑到这一点,因为C++支持多重继承,所以一个类可以已经成为多种不同的基本类型。

此外,所谓的diamond problem已经间接支持了这种情况,其中多个(不同的)基类可以从公共(public)基类继承。

我在Wikipedia上找到了几句话,似乎暗示这是OO语言中的常见但非普遍性约束,可能仅是因为担心语法复杂性,并且是不受欢迎的约束:

Wikipedia: Inheritance (object-oriented programming)#Design constraints



Wikipedia: Multiple Inheritance#Mitigation



我不同意该声明“...,因为没有办法限定要使用的父类(super class)”。编译器不能只允许对基本限定符进行某种索引,例如Animal[1]::method()(并且同样可以允许将类定义中的重复折叠为struct DoubleAgent : public Agent[2] {};)?我认为没有任何问题,从语法和概念上来说这不是一个简单的解决方案吗?

我相信我已经找到了两种可能的解决方法,尽管我不能肯定地说它们是否会在实际系统中正常工作,或者它们是否会导致无法解决的问题:

1:在基类上消除模板非类型参数的歧义。
template<int N> struct Agent {};
struct DoubleAgent : public Agent<1>, public Agent<2> {};

2:消除中间基类的歧义(菱形继承(钻石问题))。

(注意:我不必在此处使用带有CRTP的模板类,但是如果重复使用此模式(反模式?),我认为这将有助于通用性和减少重复代码。)
template<typename T> struct Primary : public T {};
template<typename T> struct Secondary : public T {};

struct Agent {};
struct DoubleAgent : public Primary<Agent>, public Secondary<Agent> {};

请评论:
  • 继承/多重继承/重复继承的一般概念
  • 不支持C++(或其他语言)中的重复继承的基本原理,
  • 明显支持在Eiffel(或其他语言)中重复继承的基本原理,以及
  • 我上面显示的可能的解决方法。


  • 编辑:我想强调一下我建议的C++中重复继承的语法,因为我认为答案的重要性忽略了它的简单性和全面性。

    以下代码演示了该想法,涵盖了强制转换(@stefan对此进行了评论)和成员资格:
    struct Agent { int id; };
    struct TripleAgent : public Agent[3] {};
    
    int main() {
        TripleAgent tripleAgent;
        tripleAgent.Agent[0]::id = 1;
        tripleAgent.Agent[1]::id = 2;
        tripleAgent.Agent[2]::id = 3;
        Agent* agent0 = (Agent[0]*)tripleAgent;
        Agent* agent1 = (Agent[1]*)tripleAgent;
        Agent* agent2 = (Agent[2]*)tripleAgent;
    }
    

    如您所见,Agent[N]本质上本身就是一种类型名称,但是仅在限定或转换具有N+1或更多基础类型实例的类型时才有效。这给语言增加了很少的复杂性,非常直观(在我看来),因为它反射(reflect)了程序员已经熟悉的数组索引,并且我相信它是无所不包的(即不存在任何歧义)。

    答题者似乎面临的主要困难如下:
  • 它是复杂的,任意的或令人发指的。
  • 有不需要语言修改的替代解决方案。
  • 所需的标准/编译器更改将涉及过多的工作/工作。

  • 我的回复:
  • 不,不是;不是。我认为这是最简单,最合乎逻辑的解决方案。
  • 这些是解决方法,而不是解决方案。您不必创建一个空的类,也不必让编译器生成一个冗余的模板变体来消除父类的歧义。而且,对于模板变通方法,我认为这将在生成的二进制文件中涉及冗余,因为编译器将在每个变体(例如Agent<1>Agent<2>)生成单独的实现时,在不必要的情况下(如果我错了请纠正我;编译器是否足够聪明,知道在类定义中未使用模板参数,因此不会为该参数的每个不同参数生成单独的代码?)。
  • 我想这是最好的说法。功能的实用性可能无法证明实现该功能的合理性。但这不是反对该想法本身的论据。当我问这个问题时,我希望对这个想法进行更多的理论上,学术上的讨论,而不是“我们没有时间做这个”反应。


  • 编辑:这是另一种可能的语法,我实际上更喜欢。它允许将父类(super class)型名称作为成员进行访问,其类型为:
    struct Person { int height; };
    struct Agent { int id; };
    struct TripleAgent : public Person, public Agent[3] {};
    
    int main() {
        TripleAgent tripleAgent;
        tripleAgent.Person.height = 42;
        tripleAgent.Agent[0].id = 1;
        tripleAgent.Agent[1].id = 2;
        tripleAgent.Agent[2].id = 3;
        Person& person = tripleAgent.Person;
        Agent& agent0 = tripleAgent.Agent[0];
        Agent& agent1 = tripleAgent.Agent[1];
        Agent& agent2 = tripleAgent.Agent[2];
    }
    

    最佳答案

    为什么C++不支持此功能?
    您的编译器不接受这种多重继承的原因是,在C++标准中明确禁止使用:

    因此,不管这种继承的哲学依据如何,这都是无效的C++:

    struct DoubleAgent : public Agent, public Agent {};  // direct base class more than once !!!
    
    原因纯粹是句法上的。假设您具有以下agent定义:
    struct Agent {  double salary; };
    
    如果双重继承有效,您将无法消除要访问的两个继承工资中的哪个歧义。
    如何在C++中正确完成此操作?
    (现有的)标准解决方案是使用一个空类来消除基类将具有的多重角色的歧义:
    struct AgentFromSouth : public Agent {};
    struct AgentFromNorth : public Agent {};
    struct DoubleAgent : public AgentFromSouth, public AgentFromNorth {};  // valid
    
    歧义消除方法如下:
    DoubleAgent spy;
    spy.AgentFromSouth::salary = 10000.0;
    spy.AgentFromNorth::salary = 800.0;
    
    这不是解决方法! 中间类不是一个简单的解决方法。它们提供了一致的子类型语义:
  • 如果DoubleAgent从同一类继承两次,则该同一类实际上具有两个不同的角色。您想让这些角色保持匿名,但是标准强制您给它们起一个名字。因此,它有助于人类读者更好地理解代码。
  • 如果DoubleAgent继承自(即"is")AgentFromNorth,那么独立于多重继承,我应该写:AgentFromNorth *p = &spy;您建议的任何其他语法都需要遵守这一原则。

  • 从技术上讲,使用现代编译器时,此类空类也不会产生大量代码开销(由我的编译器生成的用于通过空类show继承的汇编代码与直接继承完全相同)。
    关于替代方法的进一步讨论
    您提出的基于模板的解决方案肯定是一个主意。但是,两个建议的替代方案都是语法糖,可以为同一基类提供两个不同的名称(Agent<1>Primary<Agent>)。这正是通过按照今天的标准创建一个中间的空“别名”类而实现的。但是还有更多的问题。
    实际上,这样的语法会引起依赖性问题。虽然对于所有类,都为现有构造明确定义了前向声明规则和类可见性,但对于您的替代方法而言,情况并非完全如此。
    例如,要定义一个指向基类的指针,我将无法编写:
    DoubleAgent *pspy = ....;
    Agent *p = pspy;  //  ambiguous:  which role should I use ?  Agent<1> or Agent<2>  ?
    
    现在您肯定会说我可以很好地写类似以下内容:
    Agent<1> *p = pspy;
    
    但是Agent<1>的含义仅在DoubleAgent的上下文中相关。它不是具有自己独立定义的类型。您将如何解释:
    class QuadrupleAgent : DoubleAgent, DoubleAgent {...};
    QuadrupleAgend *pmasterspy;
    Agent<1> *p = pmasterspy; // Ambiguous: is it DoubleAgent<1>::Agent<1> or DoubleAgent<2>::Agent<1> ?
    
    标准对所有这些问题都进行了很好的处理,并且以一致的方式进行了处理。您的短路语法会提出比解决方案更多的问题。那么,创建这种新的且繁琐的语言结构为现有的简单结构提供替代方法有什么好处?

    关于c++ - 为什么重复继承/重复继承无效?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28745185/

    10-11 23:05
    查看更多