-
Notifications
You must be signed in to change notification settings - Fork 6
Наследование, построение иерархии, множественное наследование и неоднозначности в нём
Наследование – создание нового класса на основе старого.
Три схемы наследования – приватное, публичное, протектное. По умолчанию, наследование происходит приватным.
class A
{
private:
int a;
protected:
int b;
public:
int f();
};
class B: public A //публичное наследование
{
private:
int c;
protected:
int d;
public:
int g();
};
B obj;
/* приватное наследование
а не наследуется. c, b, f- приватные, d - протектная, g - публичная
извне можно получить доступ только к g (интерфейсу). G имеет доступ к d,c,b,f. F имеет доступ к b,a
происходит смена интерфейса F на G
протектное наследование
а не наследуется. c - приватная, b,d,f - протектные, g - публичная
-''-.
смена интерфейса, но в данном случае интерфейс Ф БУДЕТ ДОСТУПЕН к классам, наследующимся из класса Б
публичное наследование
все члены сохраняют свой уровень доступа базового класса
а не наследуется.
c - приватная, b,d - протектные, f,g - публичные.
интерфейс ДОПОЛНЯЕТСЯ.
*/
В производном классе можно определить такой же метод как в базовом – одно имя, один список параметров, один тип возврата. Метод в производном классе будет доминировать над методом базового класса.
В каких случаях использовать наследование?
- наследование когда выделяем базовый класс из нескольких;
- когда есть класс, его нужно расщепить на несколько разных.
Выделение общей базы (в порядке приоритета): ВСЕГДА выделяется класс, если есть общая схема использования. Сходство между набором операций (над объектами разных классов выполняются одинаковые операции). Совершенно разный функционал и разное использование, но могут быть выделены операции, имеющие единую реализацию. Желательно выделять базу даже в том случае, когда объекты разных классов фигурируют вместе в «дискуссиях по проекту» (когда возможно в дальнейшем может проявиться общность классов).
Расщепление класса: два подмножества операций класса используются в разной манере (в разных областях программы, домена). Группы операций имеют несвязанную реализацию. Один и тот же класс фигурирует в разных, не связанных между собой частях – лучше этот класс разделить.
ООП использует рекурсивный дизайн – постепенное разворачивание программы от базовых классов к более специализированным. С++ один из немногих языков с множественным наследованием. Оно может упростить граф наследования, но также создает пучок проблем для программиста: возникает неоднозначность, которую бывает тяжело контролировать.
class A
{
public:
int a; //арара! объявление публичного поля!
int (*b)(); //указатель на функцию! тоже публичное поле!
//более того, указателей на ФУНКЦИИ ВООБЩЕ не должно быть! максимум - на методы
int f();
int f(int);
int g();
}
class B
{
int a; //допускается - приватные поля
int b;
public:
int f();
int g; //арара публичное свойство!
int h();
int h(int);
}
class C: public A, public B {};
//метод рандомного класса
X::f(C *pc)
{
pc->a = 1; //еггог! ошибка доступа
pc->b(); //то же самое!
pc->f(); //йух пойми какой Ф вызывать, то ли из А, толи из Б
pc->f(1) //перегрузка не произойдёт
pc->g = 1; //функция или переменная!?
pc->h();
pc->h(1); //а вот в этих двух всё ок
}
Также, иерархия наследования при МН может быть достаточно сложной. Введем понятие «прямой базы» - от которой НЕПОСРЕДСТВЕННО наследуется новый класс, и «косвенной базы» - прямая база прямой базы и так далее, we need to go deeper. Прямая база может использоваться один раз (: public A, public A низя!), а вот косвенная – сколько угодно. Тем не менее, желательно, чтобы база входила только один раз. Для этого используется «виртуальное наследование». Если подобъект класса был создан, идет проверка на это, и ещё раз он не создается.
class A {};
class B: virtual public A {};
class C: public A {};
class D: public B, public C {};
Всё ок.
class A {};
class B: public A {};
class C: virtual public A {};
class D: public B, public C {};
/*Не всё ок: для подобъекта класса В будет создан подобъект А. Если в В и С будут функции, меняющие что-то в А, то эти изменения будут происходить независимо друг от друга. Поэтому лучше писать виртуал и там и тут – предохранения ради.
Если в А будет f() и в B будет f(); то при вызове f() из Д вызовется доминирующий – из В.*/
class A {f();};
class B: virtual public A {f();};
class C: public B, virtual public A {};
/*Интерпретируется по-разному в зависимости от компилятора (неопределенной поведение). В билдере для С будет вызываться B::f(); в визуале – неоднозначность.*/
💠 Виртуальные методы Базовый класс может выполнять объединяющую функцию - обладать набором свойств, присущих объектам производных классам. Функционал общий, однако выполняется по разному (трамвай ездит только по рельсам, а автобус – не только). Методы должны реализовывать производные классы, а базовый задает только их интерфейс. Было решено, что указатель на элемент ЛЮБОГО класса преобразуется к указателю на элемент его базового; в базовом классе метод описывается через virtual – будут создаваться «таблицы соответствия». Жрёт время и ресурсы (нужно спуститься по таблице), но мы получаем свободу подменить одно понятие другим. НЕ ИДЕНТИЧНО доминированию – при доминировании могут вызываться методы базовых классов, а при виртуальности – методы производного.
class A {};
class B: public A {};
A *pa = NULL;
B *pb = NULL;
if (pa==pb); //неопределено!
A* index(A* p, int i)
{ //категорически запрещено для С++!!1! создание МАССИВОВ ОБЪЕКТОВ - критически атата! максимум - массив указателей на объекты
return &p[i];
}