Skip to content

Наследование, построение иерархии, множественное наследование и неоднозначности в нём

Pandas edited this page Jun 16, 2017 · 1 revision

Наследование – создание нового класса на основе старого.

Три схемы наследования – приватное, публичное, протектное. По умолчанию, наследование происходит приватным.

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];
}
Clone this wiki locally