C++. Множественное наследование. Дублирование класса и его данных. Проблема ромба

Множественное наследование. Дублирование класса и его данных. Проблема ромба


Содержание


Поиск на других ресурсах:

1. Случай неоднозначности в классе при множественном наследовании
Неоднозначность при множественном наследовании может возникнуть в случаях, когда обращение к элементу базового класса может трактоваться по-разному (двумя и более способами). В этом случае компилятор выдает соответствующее сообщение.
Например, на рисунке 1 изображен фрагмент кода и соответствующая иерархия классов, приводящих к неоднозначности и дублированию данных.
C++. Множественное наследование. Проблема ромба.
Рисунок 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 // ключевое слово virtual делает одну копию класса A
{
  ...
}

...

class C : virtual public A  // ключевое слово virtual производит одну копию класса A
{
  ...
}
Тогда иерархия классов, описанная на рисунке 1, может иметь другой вид, как показано на рисунке 2
C++. Множественное наследование. Объявление класса как виртуального
Рисунок 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// ключевое слово 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;
}

 


Связанные темы