0

В моей C++ У меня есть что-то вроде следующегонекоторые основы C++ множественного наследования

class AbstractA { 
    void Foo() = 0; 
    void Bar() = 0; 
    void Woo() = 0; 
}; 

class AbstractB : public AbstractA { 
    void Doh() = 0; 
}; 

class Obj1 : public AbstractA { 
    void Foo() {} 
    void Bar() {} 
    void Woo() {} 
}; 

Теперь я хочу, чтобы определить новый класс Obj2 который Obj1 и (реализует) AbstractB. В основном

class Obj2 : public Obj1, public AbstractB { 
    void Doh(); 
}; 

Здесь возникает ошибка компиляции.

После некоторых соображений я подозреваю, что я должен (пере) определить Foo(), Bar() и Woo() внутри Obj2 потому что компилятор не знает правильный путь, чтобы следовать, чтобы решить их (в основном при переходе от Obj1 или AbstractB?). Правильно ли я по этому поводу?

Если это так, и так как мой правильный путь для решения Foo(), Bar() и Woo() всегда проходящие от ObjA, есть ли синтаксис, чтобы избежать родительского класса вызова для каждого метода? Другими словами, я могу быть более кратким, чем

class Obj2 : public Obj1, public AbstractB { 
    void Doh(); 
    void Foo() { A::Foo() } 
    void Bar() { A::Bar() } 
    void Woo() { A::Woo() } 
} 
+1

Основной принцип, если вы хотите, чтобы сделать несколько наследований, вам нужно использовать Virtaul explicilty, другой мудрый компилятор запросит ошибку, потому что компилятор не может решить маршрут родительского Множественное наследование также известно как проблема с алмазом –

ответ

1

При использовании = 0 создавать чистые виртуальные методы, которые вы также должны использовать virtual ключевое слово, например,

class AbstractA 
{ 
    virtual void Foo() = 0; 
    virtual void Bar() = 0; 
    virtual void Woo() = 0; 
}; 

Кроме того, возможно, вы захотите сделать эти функции общедоступными. Еще одна важная вещь: всегда объявить виртуальный деструктор в классах, предназначенных для использования в наследовании, см., Например: When to use virtual destructors?.

Класс Obj1 реализует интерфейс AbstractA и, следовательно, является конкретным классом. AbstractB расширяет интерфейс AbstractA по методу Doh. Однако это вызывает проблемы, когда вы наследуете Obj1 (который наследует AbstractA) и AbstractB (который также наследует AbstractA). Таким образом, вы наследуете AbstractA 'дважды', что не работает, если вы не используете виртуальное наследование , см.: https://en.wikipedia.org/wiki/Virtual_inheritance.

Более простой подход - не допустить, чтобы AbstractB унаследовал от AbstractA.

Хороший способ объявить классы поэтому:

class AbstractA 
{ 
public: 
    virtual ~AbstractA() {} 
    virtual void Foo() = 0; 
    virtual void Bar() = 0; 
    virtual void Woo() = 0; 
}; 

class AbstractB 
{ 
public: 
    virtual ~AbstractB() {} 
    virtual void Doh() = 0; 
}; 

/* This is now a concrete class that implements the 'interface' AbstractA. */ 
class Obj1 : public AbstractA 
{ 
public: 
    void Foo() {} /* Implements AbstractA::Foo() */ 
    void Bar() {} /* Implements AbstractA::Bar() */ 
    void Woo() {} /* Implements AbstractA::Woo() */ 
}; 

/* Inherits Obj1's implementations of AbstractA and implements AbstractB */ 
class Obj2 : public Obj1, public AbstractB 
{ 
public: 
    void Doh() {} /* Implements AbstractB::Woo() */ 
}; 
2

Вы можете использовать virtual

class Obj2 : public Obj1, public virtual AbstractB { 
    void Doh(); 
}; 
2

Вы пытаетесь решить, что теперь стали печально известными в ++ сообщество страшного «Алмаз смерть Проблемы» C. Bjarne Stroustroup, создатель C++, дает классическую иллюстрацию этой проблемы.

class Base { 
    public: 
    virtual void Method1() = 0; 
    virtual void Method2() = 0; 
}; 

class Base1 : public Base 
{ 
    public: 
    virtual void Method1(); 
    virtual void Method2(); 
}; 

class Base2 : public Base 
{ 
    public: 
    virtual void Method1(); 
    virtual void Method2(); 
} 

class Concrete : public Base1, public Base2 
{ 
    virtual void Method1(); 
    virtual void Method2(); 
} 

Посмотрите на вышеуказанную иерархию классов. Как вы правильно догадались, компилятор не знает, какую версию Method1() и Method2() следует принять в определение класса Concrete.Поскольку у Base1 и Base2 есть свои собственные версии Method1() и Method2(), у компилятора есть 2 варианта выбора этих определений, и он просто запутывается и начинает бросать все эти ошибки со словом «двусмысленный», брошенным по всему месту.

Решение, предложенное Bjarne Stroustroup для проблемы, - это уловка под названием «Виртуальное наследование». Фактически, вам нужно ввести ключевое слово virtual, когда вы выходите из класса «Base».

class Base1 : virtual public Base 
{ 
    public: 
    virtual void Method1(); 
    virtual void Method2(); 
}; 

class Base2 : virtual public Base 
{ 
    public: 
    virtual void Method1(); 
    virtual void Method2(); 
} 

Это говорит компилятору, что, хотя есть несколько копий базы в Base1 и Base2, они должны по сути точки к одной и той же версии базы. Это гарантирует, что при определении: класс бетона: общественный Base1, общественный Base2 { }

«Бетон» получает только одну копию «базы», ​​которая, как «Base1» и «Base2» указывают на , тем самым устраняя двусмысленность для компилятора. Как это достигается компилятором? Каждый класс с виртуальными методами связан с чем-то, называемым таблицей виртуальных функций или vtable. Vtable имеет указатель на все виртуальные методы этого класса. Когда классы Base1 и Base2 загружаются, vtables для этих 2 классов содержат единственный указатель на класс Base. Аналогично, когда Concrete загружается, vtable для Concrete также указывает на один и тот же единственный экземпляр Base, эффективно гарантируя, что есть один экземпляр Base.

При создании экземпляров Безусловных есть моменты, которые необходимо учитывать. Вам нужно будет явно вызвать конструктор Base, точно так же, как вы явно вызываете конструкторы Base1 и Base2. Что-то вроде

Concrete() : Base(), Base1(), Base2() 
{ 
} 

Кроме того, если имеется явный вызов конструктора Base() в конструкторах Base1 и Base2, это будет пропускаться при инстанцировании бетона и конструктор базы будет вызываться непосредственно.