
- •Глава 6 посвящена понятию производных классов, которое позволяет строить
- •Раздел 3.4 главы 2. Для обозначения справочного руководства применяется
- •1991 Г.Г. (такие как множественное наследование, статические функции-члены
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •1.2.5 Объектно-ориентированное программирование
- •1.5 Поддержка объектно-ориентированного программирования
- •1.5.1 Механизм вызова
- •1.5.2 Проверка типа
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •4.3.2 Множественные заголовочные файлы
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •5.1 Введение и краткий обзор
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Vector и matrix, мы могли бы обойтись без контроля индекса при
- •5.4.5 Указатели на члены
- •5.4.6 Структуры и объединения
- •5.5.3 Свободная память
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Void f2(t a) // вариант с контролем
- •Void f3(t a) // вариант с контролем
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.4 Неявная передача операций
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •9.4.4 Особые ситуации и конструкторы
- •9.5 Особые ситуации могут не быть ошибками
- •10.1 Введение
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •Istream - шаблон типа smanip, а smanip - двойник для ioss.
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2 Классы
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •12.2.3 Зависимости в рамках иерархии классов.
- •Vertical_scrollbar или с помощью одного типа scrollbar, который
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.4 Узловые классы
- •1, 2, 6 И 7. Класс, который не удовлетворяет условию 6, походит
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
1.5.2 Проверка типа
Необходимость контроля типа при обращениях к виртуальным функциям
может оказаться определенным ограничением для разработчиков библиотек.
Например, хорошо бы предоставить пользователю класс "стек чего-угодно".
Непосредственно в С++ это сделать нельзя. Однако, используя шаблоны типа и
наследование, можно приблизиться к той эффективности и простоте
проектирования и использования библиотек, которые свойственны языкам с
динамическим контролем типов. К таким языкам относится, например, язык
Smalltalk, на котором можно описать "стек чего-угодно". Рассмотрим
определение стека с помощью шаблона типа:
template < class T > class stack
{
T * p;
int sz;
public:
stack ( int );
~stack ();
void push ( T );
T & pop ();
};
Не ослабляя статического контроля типов, можно использовать такой стек
для хранения указателей на объекты типа plane (самолет):
stack < plane * > cs ( 200 );
void f ()
{
cs.push ( new Saab900 ); // Ошибка при трансляции :
// требуется plane*, а передан car*
cs.push ( new Saab37B );
// прекрасно: Saab 37B - на самом
// деле самолет, т.е. типа plane
cs.pop () -> takeoff ();
cs.pop () -> takeoff ();
}
Если статического контроля типов нет, приведенная выше ошибка
обнаружится только при выполнении программы:
// пример динамическое контроля типа
// вместо статического; это не С++
Stack s; // стек может хранить указатели на объекты
// произвольного типа
void f ()
{
s.push ( new Saab900 );
s.push ( new Saab37B );
s.pop () -> takeoff (); // прекрасно: Saab 37B - самолет
cs.pop () -> takeoff (); // динамическая ошибка:
// машина не может взлететь
}
Для способа определения, допустима ли операция над объектом, обычно
требуется больше дополнительных расходов, чем для механизма вызова
виртуальных функций в С++.
Рассчитывая на статический контроль типов и вызов виртуальных функций,
мы приходим к иному стилю программирования, чем надеясь только на
динамический контроль типов. Класс в С++ задает строго определенный
интерфейс для множества объектов этого и любого производного класса, тогда
как в Smalltalk класс задает только минимально необходимое число операций,
и пользователь вправе применять незаданные в классе операции. Иными
словами, класс в С++ содержит точное описание операций, и пользователю
гарантируется, что только эти операции транслятор сочтет допустимыми.
1.5.3 Множественное наследование
Если класс A является базовым классом для B, то B наследует атрибуты
A. т.е. B содержит A плюс еще что-то. С учетом этого становится очевидно,
что хорошо, когда класс B может наследовать из двух базовых классов A1 и
A2. Это называется множественным наследованием.
Приведем некий типичный пример множественного наследования. Пусть есть
два библиотечных класса displayed и task. Первый представляет задачи,
информация о которых может выдаваться на экран с помощью некоторого
монитора, а второй - задачи, выполняемые под управлением некоторого
диспетчера. Программист может создавать собственные классы, например,
такие:
class my_displayed_task: public displayed, public task
{
// текст пользователя
};
class my_task: public task {
// эта задача не изображается
// на экране, т.к. не содержит класс displayed
// текст пользователя
};
class my_displayed: public displayed
{
// а это не задача
// т.к. не содержит класс task
// текст пользователя
};
Если наследоваться может только один класс, то пользователю доступны
только два из трех приведенных классов. В результате либо получается
дублирование частей программы, либо теряется гибкость, а, как правило,
происходит и то, и другое. Приведенный пример проходит в С++ безо всяких
дополнительных расходов времени и памяти по сравнению с программами, в
которых наследуется не более одного класса. Статический контроль типов от
этого тоже не страдает.
Все неоднозначности выявляются на стадии трансляции:
class task
{
public:
void trace ();
// ...
};
class displayed
{
public:
void trace ();
// ...
};
class my_displayed_task:public displayed, public task
{
// в этом классе trace () не определяется
};
void g ( my_displayed_task * p )
{
p -> trace (); // ошибка: неоднозначность
}
В этом примере видны отличия С++ от объектно-ориентированных диалектов
языка Лисп, в которых есть множественное наследование. В этих диалектах
неоднозначность разрешается так: или считается существенным порядок
описания, или считаются идентичными объекты с одним и тем же именем в
разных базовых классах, или используются комбинированные способы, когда
совпадение объектов доля базовых классов сочетается с более сложным
способом для производных классов. В С++ неоднозначность, как правило,
разрешается введением еще одной функции:
class my_displayed_task:public displayed, public task
{
// ...
public:
void trace ()
{
// текст пользователя
displayed::trace (); // вызов trace () из displayed
task::trace (); // вызов trace () из task
}
// ...
};
void g ( my_displayed_task * p )
{
p -> trace (); // теперь нормально
}
1.5.4 Инкапсуляция
Пусть члену класса (неважно функции-члену или члену, представляющему
данные) требуется защита от "несанкционированного доступа". Как разумно
ограничить множество функций, которым такой член будет доступен? Очевидный
ответ для языков, поддерживающих объектно-ориентированное
программирование, таков: доступ имеют все операции, которые определены для
этого объекта, иными словами, все функции-члены. Например:
class window
{
// ...
protected:
Rectangle inside;
// ...
};
class dumb_terminal : public window
{
// ...
public:
void prompt ();
// ...
};
Здесь в базовом классе window член inside типа Rectangle описывается
как защищенный (protected), но функции-члены производных классов,
например, dumb_terminal::prompt(), могут обратиться к нему и выяснить, с
какого вида окном они работают. Для всех других функций член
window::inside недоступен.
В таком подходе сочетается высокая степень защищенности
(действительно, вряд ли вы "случайно" определите производный класс) с
гибкостью, необходимой для программ, которые создают классы и используют
их иерархию (действительно, "для себя" всегда можно в производных классах
предусмотреть доступ к защищенным членам).
Неочевидное следствие из этого: нельзя составить полный и
окончательный список всех функций, которым будет доступен защищенный член,
поскольку всегда можно добавить еще одну, определив ее как функцию-член в
новом производном классе. Для метода абстракции данных такой подход часто
бывает мало приемлемым. Если язык ориентируется на метод абстракции
данных, то очевидное для него решение - это требование указывать в
описании класса список всех функций, которым нужен доступ к члену. В С++
для этой цели используется описание частных (private) членов. Оно
использовалось и в приводившихся описаниях классов complex и shape.
Важность инкапсуляции, т.е. заключения членов в защитную оболочку,
резко возрастает с ростом размеров программы и увеличивающимся разбросом
областей приложения. В $$6.6 более подробно обсуждаются возможности языка
по инкапсуляции.