Правила программирования на С и С++. Главы 7-8 - Наследование - это процесс добавления полей данных и методов-членов
ОГЛАВЛЕНИЕ
96. Объект производного класса является объектом базового класса.
97. Наследование - это процесс добавления полей данных и методов-членов.
В С++ производный класс может рассматриваться как механизм добавления полей данных и обработчиков сообщений к существующему определению класса - к базовому классу. (Вы можете также смотреть на наследование как на средство изменения поведения объекта базового класса при получении им конкретного сообщения. Я вернусь к такой точке зрения при обсуждении виртуальных функций). В таком случае иерархия классов является просто средством представления полей данных и методов, определяемых для конкретного объекта. Объект содержит все данные и методы, объявленные на его уровне, а также на всех вышележащих уровнях.
Общая ошибка, совершаемая начинающими программистами на С++, состоит в том, что они смотрят на иерархию классов и думают, что сообщения передаются от объектов производного класса к объектам базового класса. Помните, что иерархия классов С++ не существует во время выполнения. Все, что у вас есть во время выполнения, это фактические объекты, чьи поля определяются во время компиляции при помощи иерархии классов.
В этом вопросе путаница создана многими книгами по языку Smalltalk, описывающими реализацию во время выполнения системы обработки сообщений так, как если бы сообщения передавались от производного к базовому классу.5 Это просто неверно (и в случае Smalltalk, и в случае С++). С++ использует наследование. Производный класс - это тот же базовый класс, но с несколькими добавленными полями и обработчиками сообщений. Следовательно, когда объект С++ получает сообщение, он или обрабатывает его, или нет; он или определяет обработчик, или получает его в наследство. Если ни то, ни другое не имеет места, то сообщение просто не может быть обработано. И оно никуда не передается.
Не путайте отношение наследования с объединением. При объединении в один класс (контейнер) вложен объект другого класса (в отличие от наследования от другого класса). Объединение в действительности лучше, чем наследование, если у вас есть возможность выбора, потому что отношения сцепления между вложенным объектом и внешним миром гораздо слабее, чем отношения между базовым классом и внешним миром.
Объединение позволяет методам контейнера действовать подобно фильтру, через который передаются сообщения, предназначенные для вложенного объекта. Обработчики сообщений часто будут иметь одинаковые имена в контейнере и во вложенном объекте. Например:
class string // строка{
// ...public: const string ?operator=( const string ?r );};class numeric_string // строка, содержащая число
{
string str;// ...
public: const string ?operator=( const string ?r );}const string ?numeric_string::operator=( const string ?r )
{
if( r.all_characters_are_digits() ) // все символы - цифры str = r;else throw invalid_assignment();return *this;}Это на самом деле довольно слабый пример объединения, потому что, если бы функция operator=() была виртуальной в базовом классе, то объект numeric_string мог бы наследовать от string и заместить оператор присваивания для проверки на верное числовое значение. С другой стороны, если перегруженная операция сложения + в классе string выполняет конкатенацию, то вам может понадобиться перегрузить + в классе numeric_string для выполнения арифметического сложения (т.е. преобразовывать строки в числа, которые складывать и затем присваивать результат строке). Объединение в последнем случае решило бы немного проблем.Возвращаясь к наследованию, отметим, что объекты классов, показанных в таблице 3, вероятно будут размещаться в памяти одинаково. Каждое из этих определений, как вы заметили, имеет компонент some_cls, но доступ к этому компоненту требует совершенно разных процедур и механизмов. В этой книге я использую выражение "компонент базового класса" по отношению к той части объекта, которая определена на уровне базового класса, а не к вложенному объекту. То есть, когда я говорю, что объект производного класса имеет "компонент базового класса", то имею в виду, что некоторые из его полей и обработчиков сообщений определены на уровне базового класса. При рассмотрении вложенного объекта я буду называть его "полем" или "вложенным объектом".
Таблица 3. Два определения класса, одинаково представляемые на уровне машинного кода.Объединение | Наследование |
class container { some_cls contained; // ... }; | class base : public some_cls { // ... }; |