Множинна спадковість. Дублювання класу та його даних. Проблема ромба
Зміст
- 1. Випадок неоднозначності у класі при множинній спадковості
- 2. Уникнення дублювання коду та неоднозначності. Оголошення класу віртуальним
- 3. Приклад коду, що призводить до неоднозначності виклику
- 4. Приклад коду, що вирішує проблему неоднозначного виклику та дублювання даних класу. Оголошення класу віртуальним
- Споріднені теми
Пошук на інших ресурсах:
1. Випадок неоднозначності у класі при множинній спадковості
Неоднозначність при множинній спадковості може виникнути у випадках, коли звертання до елементу базового класу може трактуватись по різному (двома і більше способами). У цьому випадку компілятор видає відповідне повідомлення.
Наприклад, на рисунку 1 зображено фрагмент коду та відповідна ієрархія класів які призводять до неоднозначності та дублювання даних.
Рисунок 1. Ієрархія класів, при якій звертання елементів класу A може викликати неоднозначне трактування а також дублювання даних класу
При використанні коду на рисунку 1 дані класу A дублюються, тобто використовуються два рази. Також звертання до protected-змінної з іменем value класу A, наприклад,
value
видасть помилку компіляції: неоднозначний виклик (ambiguous call).
Щоб виправити цю помилку потрібно чітко вказати, через який клас відбувається виклик. Тобто вказати наступний рядок у функції D::GetValue()
return B::value;
або
return C::value;
⇑
2. Уникнення дублювання коду та неоднозначності. Оголошення класу віртуальним
Щоб уникнути дублювання коду та даних класу 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 при успадкуванні
- Порядок виклику конструкторів при спадковості. Обмеження спадковості. Властивості покажчика на базовий клас
⇑