C++ 为什么重复继承/重复继承无效?

C++ 为什么重复继承/重复继承无效?,c++,inheritance,multiple-inheritance,language-lawyer,C++,Inheritance,Multiple Inheritance,Language Lawyer,此代码无效: 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 {};

此代码无效:

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-a关系,那么它不应该与基类有is-two-of-a关系吗?特别是考虑到C++支持多重继承,一个类已经可以<强> > < /强>多个不同的基类型。 此外,所谓的基类已经间接地支持了这一点,其中多个(不同的)基类可以从公共基类继承

我从维基百科中找到了几句话,似乎暗示这是OO语言中常见但不普遍的约束,可能只是因为担心语法复杂性,这是一个不受欢迎的约束:


设计约束 在设计程序时广泛使用继承会施加某些约束

例如,考虑一个包含人名的类人, 出生日期、地址和电话号码。我们可以定义 名为Student的人员,包含该人员的平均分数 类,还有另一个叫做Employee的子类 包含此人的职务、雇主和工资

在定义这个继承层次结构时,我们已经定义了某些 限制,并非所有限制都是可取的:

  • 单一性:使用单一继承,子类只能从一个超类继承。继续上面给出的示例,Person 可以是学生也可以是员工,但不能同时是学生和员工。使用多个 继承部分地解决了这个问题,因为可以定义 从Student和Employee继承的StudentEmployee类。 但是,在大多数实现中,它仍然可以从每个 超类只有一次,因此不支持 学生有两份工作或就读两所学校。遗产 埃菲尔铁塔提供的模型通过支持 重复遗传

C++不支持显式重复继承,因为无法限定要使用哪个超类(即,让一个类在单个派生列表中出现多次[类Dog:public Animal,Animal])


我不同意这种说法“……因为没有办法限定使用哪个超类”。编译器不能只允许对基本限定符进行某种类型的索引,例如
Animal[1]::method()
(同样,也可以允许将类定义中的重复折叠为
struct DoubleAgent:public Agent[2]{};
)?我看不出有什么问题,从语法和概念上来说,这不是一个非常简单的解决方案吗

我相信我已经找到了一些可能的解决方法,尽管我不能确定它们是否会在实际系统中工作,或者它们是否会导致无法克服的问题:

1:消除基类上模板非类型参数的歧义。

template<int N> struct Agent {};
struct DoubleAgent : public Agent<1>, public Agent<2> {};
正如您所看到的,
Agent[N]
基本上本身就是一个类型名,但只有在限定或强制转换具有基础类型的
N+1
或更多实例的类型时才有效。这给语言增加了很少的复杂性,这是非常直观的(在我看来),因为它反映了程序员已经熟悉的数组索引,并且我相信它是包罗万象的(即没有留下任何歧义)

回答者似乎面临的主要困难如下:

DoubleAgent spy; 
spy.AgentFromSouth::salary = 10000.0; 
spy.AgentFromNorth::salary = 800.0;
  • 这是复杂的,武断的,或令人愤怒的
  • 还有一些不需要修改语言的替代解决方案
  • 所需的标准/编译器更改将涉及太多的工作量/工作
  • 我的答复:

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

  • Edit:这里是另一种可能的语法,我实际上更喜欢这种语法。它允许超类型名称作为成员访问,这有点像:

    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];
    }
    
    我不同意这种说法“……因为没有办法限定使用哪个超类”

    当他们说“无法限定要使用哪个超类”时,他们的意思是“如果不发明额外的语法,就无法限定要使用哪个超类”假设它是一个数组就是这样一种发明出来的语法;例如,你可以发明无数与其他语法结构不同的语法

    Animal@::method()  // @ means "first"
    Animal@@::method() // @@ means "second"
    
    我的语法看起来完全是武断的,但你的也一样

    当然,通过使语言更加复杂,也可以有其他不太离谱的方法来解决这个问题。然而,关键问题是,使语言更加复杂会带来什么好处
    Animal@::method()  // @ means "first"
    Animal@@::method() // @@ means "second"
    
    class Agent{... std::string loyality;};
    class PrimaryRole : public Agent {};
    class SecondaryRole : public Agent{};
    
    class DoubleAgent : public PrimaryRole, public SecondaryRole
    {
       void setLoyalities()
       {
           PrimaryRole::loyality = "GoodGuys";
           SecondaryRole::loyality = "BadGuys";
       }
    };
    
    class DoubleAgent : public Agent, public SecondaryRole {...};
    
    struct DoubleAgent : public Agent, public Agent {};  // direct base class more than once !!!
    
    struct Agent {  double salary; }; 
    
    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 *pspy = ....; 
    Agent *p = pspy;  //  ambiguous:  which role should I use ?  Agent<1> or Agent<2>  ? 
    
    Agent<1> *p = pspy;   
    
    class QuadrupleAgent : DoubleAgent, DoubleAgent {...};  
    QuadrupleAgend *pmasterspy;
    Agent<1> *p = pmasterspy; // Ambiguous: is it DoubleAgent<1>::Agent<1> or DoubleAgent<2>::Agent<1> ? 
    
    struct Agent
    {
         int height;
    };
    
    struct DoubleAgent: Agent, Agent
    {
    
    };
    
    int main()
    {
          DoubleAgent a;
          a.height = 42;     //  this would be ambiguous
    }
    
     a.Agent::height = 42;
    
    struct TripleAgent: Agent, Agent, Agent
    {
    
    };
    
    struct QuadrupleAgent: Agent, Agent, Agent, Agent
    {
    
    };
    
    // etc etc