Множественное наследование. Дублирование класса и его данных. Проблема ромба
Содержание
- 1. Случай неоднозначности в классе при множественном наследовании
- 2. Избежание дублирования кода и неоднозначности. Объявление класса A виртуальным
- 3. Пример кода, который приводит к неоднозначности вызова
- 4. Пример кода решает проблему неоднозначного вызова и дублирования данных класса. Объявление класса виртуальным
- Связанные темы
Поиск на других ресурсах:
1. Случай неоднозначности в классе при множественном наследовании
Неоднозначность при множественном наследовании может возникнуть в случаях, когда обращение к элементу базового класса может трактоваться по-разному (двумя и более способами). В этом случае компилятор выдает соответствующее сообщение.
Например, на рисунке 1 изображен фрагмент кода и соответствующая иерархия классов, приводящих к неоднозначности и дублированию данных.
Рисунок 1. Иерархия классов, при которой обращение элементов класса A может вызвать неоднозначное трактование, а также дублирование данных класса
При использовании кода на рисунке 1 данные класса A дублируются, то есть используются дважды. Также обращение к protected-переменной с именем value класса A, например,
value
Выдаст ошибку компиляции: неоднозначный вызов (ambiguous call).
Чтобы исправить эту ошибку, нужно четко указать, через какой класс происходит вызов. То есть указать следующую строку в функции D::GetValue()
return B::value;
или
return C::value;
⇑
2. Избежание дублирования кода и неоднозначности. Объявление класса A виртуальным
Чтобы избежать дублирования кода и данных класса A (см. рисунок 1), нужно объявить класс A виртуальным в классах B, C:
class B : public virtual A // ключевое слово virtual делает одну копию класса A { ... } ... class C : virtual public A // ключевое слово virtual производит одну копию класса A { ... }
Тогда иерархия классов, описанная на рисунке 1, может иметь другой вид, как показано на рисунке 2
Рисунок 2. Иерархия классов, если класс A унаследован в классах B и C как virtual
Также решается проблема неоднозначности. Теперь в классе D можно спокойно обращаться к имени
A::value
или просто
value
Ошибки уже не будет
⇑
3. Пример кода, который приводит к неоднозначности вызова
В примере приводится код программы, реализующей иерархию классов, изображенную на рисунке 1. В классе D в методе Show() попытка обращения к имени a вызовет ошибку неоднозначного вызова. Чтобы исправить ситуацию, нужно явно указывать базовые классы.
B::a
или
C::a
Также в этом случае происходит дублирование кода и данных класса A.
#include <iostream> using namespace std; // Проблема дублирования класса при множественном наследовании class A { protected: int a; public: A(int a) : a(a) { } void Show() { } }; class B : public A { protected: int b; public: B(int a, int b) : A(a), b(b) { } }; class C : public A { protected: int c; public: C(int a, int c) : A(a), c(c) { } }; class D : public B, public C { public: D(int a, int b, int c) : B(a, b), C(a, c) { } void Show() { // здесь ошибка, неоднозначный вызов a // cout << a << endl; - нужно явно указывать класс cout << B::a << endl; // здесь работает, потому что класс B указан явно cout << C::a << endl; // здесь работает, потому что класс C указан явно } }; int main() { D obj(1, 2, 3); obj.Show(); }
4. Пример кода решает проблему неоднозначного вызова и дублирования данных класса. Объявление класса виртуальным
В примере демонстрируется программа, реализующая программный код, изображенный на рисунке 2.
// В данном коде класс A наследуется как виртуальный. // Проблемы неоднозначности в классе D уже нет. class A { public: int value; public: A(int value) : value(value) { } A() : value(0) { } }; class B : public virtual A // ключевое слово virtual делает одну копию класса A { public: B(int value) : A(value) { } }; class C : virtual public A // ключевое слово virtual производит одну копию класса A { public: C(int value) : A(value) { } }; class D : public B, public C { public: // Здесь нет неоднозначности, поскольку код класса A не дублируется (благодаря virtual-наследованию) int Value() { return value; } // работает D(int value) : A(value), B(value), C(value) { } D() : B(0), C(0) { } }; void main() { A objA(5); D objD(10); cout << objD.Value() << endl; }
Связанные темы
- Наследование. Общие понятия. Использование модификаторов private, protected, public при наследовании
- Порядок вызова конструкторов при наследовании. Ограничения наследования. Свойства указателя на базовый класс
⇑