C++. Бархатный путь. Часть 2 - Базовые и производные классы. Часть 2

ОГЛАВЛЕНИЕ

Объект-представитель класса C является единым блоком объектов и включает собственные данные-члены класса C, а также данные-члены классов B и A. Как известно, функции-члены классов, конструкторы и деструкторы не включаются в состав объекта и располагаются в памяти отдельно от объектов. Так что схему объекта-представителя класса можно представить, буквально удалив из схемы класса функции-члены, конструкторы и деструкторы.

Следует также иметь в виду, что на схеме класса располагаются лишь объявления данных-членов, тогда как схема объекта содержит обозначения определённых областей памяти, представляющих данные-члены конкретного объекта.

Итак, выполнение оператора определения

C MyObj;

приводит к появлению в памяти объекта под именем MyObj. Рассмотрим схему этого объекта. Её отличие от схемы класса очевидно. Здесь мы будем использовать уже известный нам метасимвол ::= (состоит из). На схеме объекта информация о типе данного-члена будет заключаться в круглые скобки.

MyObj::=
A
(int)x0
B
(int)x1
(int)x2
(int)xx
C
(int)x1
(int)x2
(int)xx

Перед нами объект сложной структуры, в буквальном смысле собранный на основе нескольких классов. В его создании принимали участие несколько конструкторов. Порядок их вызова строго регламентирован. Вначале вызываются конструкторы базовых классов. Следом вызываются конструкторы производных классов.

Благодаря реализации принципа наследования, объект представляет собой цельное сооружение. Из объекта можно вызвать функции-члены базовых объектов. Эти функции наследуются производным классом от своих прямых и косвенных базовых классов. Непосредственно от объекта возможен доступ ко всем данным-членам. Данные-члены базовых классов также наследуются производными классами.

Если переопределить деструкторы базовых и производных классов таким образом, чтобы они сообщали о начале своего выполнения, то за вызовом деструктора производного класса C непосредственно из объекта MyObj:

MyObj.~C();

последует серия сообщений о выполнении деструкторов базовых классов. Разрушение производного объекта сопровождается разрушением его базовых компонентов. Причём порядок вызова деструкторов противоположен порядку вызова конструкторов.

А вот вызвать деструктор базового класса из объекта производного класса невозможно:

MyObj.~B(); // Так нельзя. Это ошибка!

Частичное разрушение объекта в C++ не допускается. БАЗОВЫЕ ДЕСТРУКТОРЫ НЕ НАСЛЕДУЮТСЯ. Таков один из принципов наследования.

Если бы можно было вызывать конструктор непосредственно из объекта, аналогичное утверждение о наследовании можно было бы сделать и по поводу конструкторов.

Однако утверждение о том, что базовый конструктор не наследуется так же корректно, как и утверждение о том, что стиральная машина не выполняет фигуры высшего пилотажа. Стиральная машина в принципе не летает. НИ ОДИН КОНСТРУКТОР (ДАЖЕ КОНСТРУКТОР ПРОИЗВОДНОГО КЛАССА) НЕ ВЫЗЫВАЕТСЯ ИЗ ОБЪЕКТА.

К моменту начала разбора структуры производного класса, транслятору становятся известны основные характеристики базовых классов. Базовые классы включаются в состав производных классов в качестве составных элементов. Это означает, что в производном классе (в его функциях) можно обращаться к данным-членам и вызывать функции-члены базовых классов. Можно, если только этому ничего не мешает (о том, что может этому помешать - немного позже).

Как раз в нашем случае в этом смысле всё в порядке, и мы приступаем к модификации исходного кода нашей программы.

Прежде всего, изменим код функции с именем f1, объявленной в классе C. Мы оставим в классе лишь её объявление, а саму функцию определим вне класса, воспользовавшись при этом её квалифицированным именем.

Проблемы, связанные с одноименными членами класса решаются с помощью операции разрешения области видимости. Впрочем, нам это давно известно:

int C ::f1()
{
A::f0();
/*Вызов функции-члена класса A.*/
f0();
/*
Для вызова этой функции можно не использовать специфицированного
имени. Функция под именем f0 одна на все классы. И транслятор
безошибочно определяет её принадлежность.
*/
A::x0 = 1;
B::x0 = 2;
C::x0 = 3;
x0 = 4;
/*
К моменту разбора этой функции-члена, транслятору известна структура
всех составных классов. Переменная x0 (как и функция f0) обладает
уникальным именем и является общим достоянием базовых и производных
классов. При обращении к ней может быть использовано как её собственное
имя, так и имя с любой квалификацией. Аналогичным образом может быть
также вызвана и функция f0().
*/
B::f0();
C::f0();
/* Изменение значений данных-членов. */
//A::x1 = 1;
/* Ошибка! Переменная x1 в классе A не объявлялась.*/
B::x1 = 2;
C::x1 = 3;
x1 = 4;
/*
Переменная x1 объявляется в двух классах. Транслятор определяет
принадлежность данных-членов по квалифицированным именам. В последнем
операторе присвоения транслятор считает переменную x1 собственностью
класса C, поскольку этот оператор располагается "на территории" этого
класса. Если бы класс C не содержал объявления переменной x1, последние
три оператора были бы соотнесены с классом B.
*/
//A::xx = 1;
/* Ошибка! Переменная xx в классе A не объявлялась.*/
B::xx = 2;
C::xx = 3;
xx = 4;
/*
Аналогичным образом обстоят дела с переменной xx, объявленной
в классе B. Хотя xx не объявлялась в классе C, транслятор
рассматривает эту переменную как элемент этого класса и не
возражает против квалифицированного имени C::xx.
В последнем операторе транслятор рассматривает переменную xx как
член класса B.
*/
return 150;
}
Теперь переопределим функцию-член класса B.
При её разборе (даже если определение этой функции располагается
после объявления класса C), транслятор воспринимает лишь имена
базовых классов. В это время транслятор забывает о существовании
класса C. А потому упоминание этого имени воспринимается им как ошибка.
int B ::f1()
{
A::f0();
A::x0 = 1;
B::x0 = 2;
//C::x0 = 3;
/* Ошибка. */
x0 = 4;
B::f0();
//C::f0();
/* Ошибка. */
/* Изменение значений данных-членов. */
//A::x1 = 1;
/* Ошибка. Переменная x1 в классе A не объявлялась.*/
B::x1 = 2;
//C::x1 = 3;
/* Ошибка. */
x1 = 4;
//A::xx = 1;
/* Ошибка! Переменная xx в классе A не объявлялась.*/
B::xx = 2;
//C::xx = 3;
/* Ошибка. */
xx = 4;
return 100;
}