Множественное наследование в C ++ - это мощный, но хитрый инструмент, который при неосторожном использовании часто приводит к проблемам, например к проблеме Diamond.
В этой статье мы обсудим проблему Diamond, как она возникает из-за множественного наследования и что вы можете сделать, чтобы решить эту проблему.
Множественное наследование в C ++
Множественное наследование - это особенность объектно-ориентированного программирования (ООП) где подкласс может наследовать более чем от одного суперкласса. Другими словами, дочерний класс может иметь более одного родителя.
На рисунке ниже показано графическое изображение множественного наследования.
На приведенной выше диаграмме класс C имеет класс А а также класс B как его родители.
Если рассматривать реальный сценарий, ребенок наследуется от отца и матери. Таким образом, Child может быть представлен как производный класс с «Отец» и «Мать» в качестве его родителей. Точно так же у нас может быть много таких реальных примеров множественного наследования.
При множественном наследовании конструкторы унаследованного класса выполняются в том порядке, в котором они унаследованы. С другой стороны, деструкторы выполняются в порядке, обратном их наследованию.
Теперь проиллюстрируем множественное наследование и проверим порядок построения и уничтожения объектов.
Иллюстрация кода множественного наследования
Для иллюстрации множественного наследования мы точно запрограммировали приведенное выше представление на C ++. Код программы приведен ниже.
#включают
используя пространство имен std;
class A // базовый класс A с конструктором и деструктором
{
общественность:
A () {cout << "класс A:: Конструктор" << endl; }
~ A () {cout << "class A:: Destructor" << endl; }
};
class B // базовый класс B с конструктором и деструктором
{
общественность:
B () {cout << "класс B:: Конструктор" << endl; }
~ B () {cout << "class B:: Destructor" << endl; }
};
class C: public B, public A // производный класс C наследует класс A, а затем класс B (обратите внимание на порядок)
{
общественность:
C () {cout << "класс C:: Конструктор" << endl; }
~ C () {cout << "class C:: Destructor" << endl; }
};
int main () {
C c;
возврат 0;
}
Результат, который мы получаем из приведенной выше программы, выглядит следующим образом:
класс B:: Конструктор
класс A:: Конструктор
класс C:: Конструктор
класс C:: Деструктор
класс A:: Деструктор
класс B:: Деструктор
Теперь, если мы проверим вывод, мы увидим, что конструкторы вызываются в порядке B, A и C, а деструкторы - в обратном порядке. Теперь, когда мы знаем основы множественного наследования, мы переходим к обсуждению проблемы алмаза.
Разъяснение проблемы алмаза
Проблема с бриллиантами возникает, когда дочерний класс наследуется от двух родительских классов, которые имеют общий родительский класс. Это показано на схеме ниже:
Здесь у нас есть класс Ребенок наследование от классов Отец а также Мать. Эти два класса, в свою очередь, наследуют класс Человек потому что и Отец, и Мать - Личность.
Как показано на рисунке, класс Child наследует черты класса Person дважды - один раз от отца, а второй - от матери. Это вызывает двусмысленность, поскольку компилятор не понимает, в каком направлении двигаться.
Этот сценарий приводит к появлению ромбовидного графа наследования, получившего известное название «Алмазная проблема».
Кодовая иллюстрация проблемы алмаза
Ниже мы представили приведенный выше пример наследования в форме ромба программно. Код приведен ниже:
#включают
используя пространство имен std;
class Person {// класс Person
общественность:
Person (int x) {cout << "Person:: Person (int) called" << endl; }
};
class Father: public Person {// класс Отец наследует Person
общественность:
Отец (int x): Person (x) {
cout << "Отец:: Отец (int) называется" << endl;
}
};
class Mother: public Person {// class Mother наследует Person
общественность:
Мать (int x): Person (x) {
cout << "Mother:: Mother (int) called" << endl;
}
};
class Child: public Father, public Mother {// Ребенок наследует отца и мать
общественность:
Ребенок (int x): Мать (x), Отец (x) {
cout << "Вызывается Child:: Child (int)" << endl;
}
};
int main () {
Ребенок ребенок (30);
}
Ниже приводится результат работы этой программы:
Person:: Person (int) вызывается
Отец:: Отец (int) называется
Person:: Person (int) вызывается
Мать:: Мать (int) называется
Вызывается Child:: Child (int)
Теперь вы можете увидеть здесь неоднозначность. Конструктор класса Person вызывается дважды: один раз при создании объекта класса «Отец», а затем - при создании объекта класса «Мать». Свойства класса Person наследуются дважды, что вызывает неоднозначность.
Поскольку конструктор класса Person вызывается дважды, деструктор также будет вызываться дважды при разрушении объекта класса Child.
Теперь, если вы правильно поняли проблему, давайте обсудим решение проблемы с бриллиантом.
Как исправить проблему с бриллиантом в C ++
Решение проблемы с бриллиантом - использовать виртуальный ключевое слово. Мы превращаем два родительских класса (которые наследуют от одного и того же класса дедушки и бабушки) в виртуальные классы, чтобы избежать двух копий класса дедушки и бабушки в дочернем классе.
Давайте изменим иллюстрацию выше и проверим вывод:
Иллюстрация кода для решения проблемы с алмазом
#включают
используя пространство имен std;
class Person {// класс Person
общественность:
Person () {cout << "Person:: Person (), вызываемый" << endl; } // Базовый конструктор
Person (int x) {cout << "Person:: Person (int) called" << endl; }
};
class Father: virtual public Person {// отец класса наследует Person
общественность:
Отец (int x): Person (x) {
cout << "Отец:: Отец (int) называется" << endl;
}
};
class Mother: virtual public Person {// класс Mother наследует Person
общественность:
Мать (int x): Person (x) {
cout << "Mother:: Mother (int) called" << endl;
}
};
class Child: public Father, public Mother {// class Child наследует отца и мать
общественность:
Ребенок (int x): Мать (x), Отец (x) {
cout << "Вызывается Child:: Child (int)" << endl;
}
};
int main () {
Ребенок ребенок (30);
}
Здесь мы использовали виртуальный ключевое слово, когда классы Отец и Мать наследуют класс Person. Обычно это называется «виртуальным наследованием», которое гарантирует передачу только одного экземпляра унаследованного класса (в данном случае класса Person).
Другими словами, класс Child будет иметь единственный экземпляр класса Person, совместно используемый классами «Отец» и «Мать». Наличие единственного экземпляра класса Person устраняет неоднозначность.
Вывод приведенного выше кода приведен ниже:
Вызывается Person:: Person ()
Отец:: Отец (int) называется
Мать:: Мать (int) называется
Вызывается Child:: Child (int)
Здесь вы можете видеть, что конструктор класса Person вызывается только один раз.
В отношении виртуального наследования следует отметить, что даже если параметризованный конструктор Класс Person явно вызывается конструкторами классов Отец и Мать через инициализацию. списки, будет вызываться только базовый конструктор класса Person.
Это связано с тем, что существует только один экземпляр виртуального базового класса, который используется несколькими классами, наследующими от него.
Чтобы предотвратить многократное выполнение базового конструктора, конструктор виртуального базового класса не вызывается классом, наследующим от него. Вместо этого конструктор вызывается конструктором конкретного класса.
В приведенном выше примере класс Child напрямую вызывает базовый конструктор для класса Person.
Связанный: Руководство для начинающих по стандартной библиотеке шаблонов в C ++
Что, если вам нужно выполнить параметризованный конструктор базового класса? Вы можете сделать это, явно вызвав его в классе Child, а не в классах «Отец» или «Мать».
Проблема алмаза в C ++, решена
Проблема ромба - это неоднозначность, которая возникает при множественном наследовании, когда два родительских класса наследуются от одного и того же родительского класса, а оба родительских класса наследуются одним дочерним классом. Без использования виртуального наследования дочерний класс дважды унаследовал бы свойства родительского класса, что привело бы к двусмысленности.
Это может часто возникать в реальном коде, поэтому важно устранять эту двусмысленность всякий раз, когда она обнаруживается.
Проблема с бриллиантом решается с помощью виртуального наследования, в котором виртуальный ключевое слово используется, когда родительские классы наследуются от общего класса дедушки и бабушки. Таким образом создается только одна копия класса grandparent, а создание объекта класса grandparent выполняется дочерним классом.
Хотите научиться программированию, но не знаете, с чего начать? Эти проекты и руководства по программированию для начинающих начнут вас.
Читать далее
- Программирование
- Программирование на C
Подписывайтесь на нашу новостную рассылку
Подпишитесь на нашу рассылку, чтобы получать технические советы, обзоры, бесплатные электронные книги и эксклюзивные предложения!
Нажмите здесь, чтобы подписаться