- •Глава 6 посвящена понятию производных классов, которое позволяет строить
- •Раздел 3.4 главы 2. Для обозначения справочного руководства применяется
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •1.2.2 Модульное программирование
- •1.2.3 Абстракция данных
- •1.2.4 Пределы абстракции данных
- •1.2.5 Объектно-ориентированное программирование
- •1.3 "Улучшенный с"
- •1.3.1 Программа и стандартный вывод
- •1.3.2 Переменные и арифметические операции
- •1.3.3 Указатели и массивы
- •1.3.4 Условные операторы и циклы
- •1.3.5 Функции
- •1.3.6 Модули
- •1.4 Поддержка абстракции данных
- •1.4.1 Инициализация и удаление
- •1.4.2 Присваивание и инициализация
- •1.4.3 Шаблоны типа
- •1.4.4 Обработка особых ситуаций
- •1.4.5 Преобразования типов
- •1.4.6 Множественные реализации
- •1.5 Поддержка объектно-ориентированного программирования
- •1.5.1 Механизм вызова
- •1.5.2 Проверка типа
- •1.5.3 Множественное наследование
- •1.5.4 Инкапсуляция
- •1.6 Пределы совершенства
- •2.1 Описания
- •2.1.1 Область видимости
- •2.1.2 Объекты и адреса
- •2.1.3 Время жизни объектов
- •2.2 Имена
- •2.3 Типы
- •2.3.1 Основные типы
- •2.3.2 Неявное преобразование типа
- •2.3.3 Производные типы
- •2.3.4 Тип void
- •2.3.5 Указатели
- •2.3.6 Массивы
- •2.3.7 Указатели и массивы
- •2.3.8 Структуры
- •2.3.9 Эквивалентность типов
- •2.3.10 Ссылки
- •2.4 Литералы
- •2.4.1 Целые константы
- •2.4.2 Константы с плавающей точкой
- •2.4.3 Символьные константы
- •2.4.4 Строки
- •2.4.5 Нуль
- •2.5 Поименованные константы
- •2.5.1. Перечисления
- •2.6. Экономия памяти
- •2.6.1 Поля
- •2.6.2. Объединения
- •2.7 Упражнения
- •3.1 Калькулятор
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •3.1.3 Таблица имен
- •3.1.4 Обработка ошибок
- •3.1.5 Драйвер
- •3.1.6 Параметры командной строки
- •3.2 Сводка операций
- •3.2.1 Скобки
- •3.2.2 Порядок вычислений
- •3.2.3 Инкремент и декремент
- •3.2.4 Поразрядные логические операции
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •3.3 Сводка операторов
- •3.3.1 Выбирающие операторы
- •3.3.2 Оператор goto
- •3.4 Комментарии и расположение текста
- •3.5 Упражнения
- •4.1 Введение
- •4.2 Связывание
- •4.3 Заголовочные файлы
- •4.3.1 Единственный заголовочный файл
- •4.3.2 Множественные заголовочные файлы
- •4.4 Связывание с программами на других языках
- •4.5 Как создать библиотеку
- •4.6 Функции
- •4.6.1 Описания функций
- •4.6.2 Определения функций
- •4.6.3 Передача параметров
- •4.6.4 Возвращаемое значение
- •4.6.5 Параметр-массив
- •4.6.6 Перегрузка имени функции
- •4.6.7 Стандартные значения параметров
- •4.6.8 Неопределенное число параметров
- •4.6.9 Указатель на функцию
- •4.7 Макросредства
- •4.8 Упражнения
- •5.1 Введение и краткий обзор
- •5.2 Классы и члены
- •5.2.1 Функции-члены
- •5.2.2 Классы
- •5.2.3 Ссылка на себя
- •5.2.4 Инициализация
- •5.2.5 Удаление
- •5.2.6 Подстановка
- •5.3 Интерфейсы и реализации
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •5.4 Еще о классах
- •5.4.1 Друзья
- •5.4.2 Уточнение имени члена
- •5.4.3 Вложенные классы
- •5.4.4 Статические члены
- •5.4.5 Указатели на члены
- •5.4.6 Структуры и объединения
- •5.5 Конструкторы и деструкторы
- •5.5.1 Локальные переменные
- •5.5.2 Статическая память
- •5.5.3 Свободная память
- •5.5.4 Объекты класса как члены
- •5.5.5 Массивы объектов класса
- •5.5.6 Небольшие объекты
- •5.6 Упражнения
- •6.1 Введение и краткий обзор
- •6.2 Производные классы
- •6.2.1 Функции-члены
- •6.2.2 Конструкторы и деструкторы
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.2.5 Виртуальные функции
- •6.3 Абстрактные классы
- •6.4 Пример законченной программы
- •6.4.1 Монитор экрана
- •6.4.2 Библиотека фигур
- •6.4.3 Прикладная программа
- •6.5 Множественное наследование
- •6.5.1 Множественное вхождение базового класса
- •6.5.2 Разрешение неоднозначности
- •6.5.3 Виртуальные базовые классы
- •6.6 Контроль доступа
- •6.6.1 Защищенные члены
- •6.6.2 Доступ к базовым классам
- •6.7 Свободная память
- •6.7.1 Виртуальные конструкторы
- •6.7.2 Указание размещения
- •6.8 Упражнения
- •7.1 Введение
- •7.2 Операторные функции
- •7.2.1 Бинарные и унарные операции
- •7.2.2 Предопределенные свойства операций
- •7.2.3 Операторные функции и пользовательские типы
- •7.3 Пользовательские операции преобразования типа
- •7.3.1 Конструкторы
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.4 Литералы
- •7.5 Большие объекты
- •7.6 Присваивание и инициализация
- •7.7 Индексация
- •7.8 Вызов функции
- •7.9 Косвенное обращение
- •7.10 Инкремент и декремент
- •7.11 Строковый класс
- •7.12 Друзья и члены
- •7.13 Предостережения
- •7.14 Упражнения
- •8.1 Введение
- •8.2 Простой шаблон типа
- •8.3 Шаблоны типа для списка
- •8.3.1 Список с принудительной связью
- •8.3.2 Список без принудительной связи
- •8.3.3 Реализация списка
- •8.3.4 Итерация
- •8.4 Шаблоны типа для функций
- •8.4.1 Простой шаблон типа для глобальной функции
- •8.4.2 Производные классы позволяют ввести новые операции
- •8.4.3 Передача операций как параметров функций
- •8.4.4 Неявная передача операций
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.5 Разрешение перегрузки для шаблонной функции
- •8.6 Параметры шаблона типа
- •8.7 Шаблоны типа и производные классы
- •8.7.1 Задание реализации с помощью параметров шаблона
- •8.8 Ассоциативный массив
- •8.9 Упражнения
- •9.1 Обработка ошибок
- •9.1.1 Особые ситуации и традиционная обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.2 Различение особых ситуаций
- •9.3 Имена особых ситуаций
- •9.3.1 Группирование особых ситуаций
- •9.3.2 Производные особые ситуации
- •9.4 Запросы ресурсов
- •9.4.1 Конструкторы и деструкторы
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •9.4.4 Особые ситуации и конструкторы
- •9.5 Особые ситуации могут не быть ошибками
- •9.6 Задание интерфейса
- •9.6.1 Неожиданные особые ситуации
- •9.7 Неперехваченные особые ситуации
- •9.8 Другие способы обработки ошибок
- •9.9 Упражнения
- •10.1 Введение
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.2.2 Вывод пользовательских типов
- •10.3 Ввод
- •10.3.1 Ввод встроенных типов
- •10.3.2 Состояния потока
- •10.3.3 Ввод пользовательских типов
- •10.4 Форматирование
- •10.4.1 Класс ios
- •10.4.1.1 Связывание потоков
- •10.4.1.2 Поля вывода
- •10.4.1.3 Состояние формата
- •10.4.1.4 Вывод целых
- •10.4.1.5 Выравнивание полей
- •10.4.1.6 Вывод плавающих чисел.
- •10.4.2 Манипуляторы
- •10.4.2.1 Стандартные манипуляторы ввода-вывода
- •10.4.3 Члены ostream
- •10.4.4 Члены istream
- •10.5 Файлы и потоки
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •10.5.3 Буферизация
- •10.6 Ввод-вывод в с
- •10.7 Упражнения
- •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 Зависимости в рамках иерархии классов.
- •12.2.4 Отношения принадлежности
- •12.2.5 Принадлежность и наследование
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •12.2.7.1 Инварианты
- •12.2.7.2 Инкапсуляция
- •12.2.8 Программируемые отношения
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.3 Абстрактные типы
- •13.4 Узловые классы
- •13.5 Динамическая информация о типе
- •13.5.1 Информация о типе
- •13.5.2 Класс Type_info
- •13.5.3 Как создать систему динамических запросов о типе
- •13.5.4 Расширенная динамическая информация о типе
- •13.5.5 Правильное и неправильное использование динамической
- •13.6 Обширный интерфейс
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.9 Управляющие классы
- •13.10 Управление памятью
- •13.10.1 Сборщик мусора
- •13.10.2 Контейнеры и удаление
- •13.10.3 Функции размещения и освобождения
- •13.11 Упражнения
9.6 Задание интерфейса
Запуск или перехват особой ситуации отражается на взаимоотношениях
функций. Поэтому имеет смысл задавать в описании функции множество
особых ситуаций, которые она может запустить:
void f(int a) throw (x2, x3, x4);
В этом описании указано, что f() может запустить особые ситуации
x2, x3 и x4, а также ситуации всех производных от них типов, но
больше никакие ситуации она не запускает. Если функция перечисляет свои
особые ситуации, то она дает определенную гарантию всякой вызывающей ее
функции, а именно, если попытается запустить иную особую ситуацию, то
это приведет к вызову функции unexpected().
Стандартное предназначение unexpected() состоит в вызове функции
terminate(), которая, в свою очередь, обычно вызывает abort().
Подробности даны в $$9.7.
По сути определение
void f() throw (x2, x3, x4)
{
// какие-то операторы
}
эквивалентно такому определению
void f()
{
try {
// какие-то операторы
}
catch (x2) { // повторный запуск
throw;
}
catch (x3) { // повторный запуск
throw;
}
catch (x4) { // повторный запуск
throw;
}
catch (...) {
unexpected();
}
}
Преимущество явного задания особых ситуаций функции в ее описании
перед эквивалентным способом, когда происходит проверка на
особые ситуации в теле функции, не только в более краткой записи.
Главное здесь в том, что описание функции входит в ее интерфейс,
который видим для всех вызывающих функций. С другой стороны,
определение функции может и не быть универсально доступным.
Даже если у вас есть исходные тексты всех библиотечных функций,
обычно желание изучать их возникает не часто.
Если в описании функции не указаны ее особые ситуации, считается,
что она может запустить любую особую ситуацию.
int f(); // может запустить любую особую ситуацию
Если функция не будет запускать никаких особых ситуаций, ее можно
описать, явно указав пустой список:
int g() throw (); // не запускает никаких особых ситуаций
Казалось было бы логично, чтобы по умолчанию функция не запускала
никаких особых ситуаций. Но тогда пришлось бы описывать свои особые
ситуации практически для каждой функции Это, как правило,
требовало бы ее перетрансляции, а кроме того препятствовало бы общению
с функциями, написанными на других языках. В результате программист
стал бы стремиться отключить механизм особых ситуаций и писал бы
излишние операторы, чтобы обойти их. Пользователь считал бы такие
программы надежными, поскольку мог не заметить подмены, но это было
бы совершенно неоправдано.
9.6.1 Неожиданные особые ситуации
Если к описанию особых ситуаций относиться не достаточно серьезно, то
результатом может быть вызов unexpected(), что нежелательно во всех
случая, кроме отладки. Избежать вызова unexpected() можно, если хорошо
организовать структуру особых ситуации и описание интерфейса. С
другой стороны, вызов unexpected() можно перехватить и сделать его
безвредным.
Если компонент Y хорошо разработан, все его особые ситуации
могут быть только производными одного класса, скажем Yerr. Поэтому,
если есть описание
class someYerr : public Yerr { /* ... */ };
то функция, описанная как
void f() throw (Xerr, Yerr, IOerr);
будет передавать любую особую ситуацию типа Yerr вызывающей функции.
В частности, обработка особой ситуации типа someYerr в f() сведется к
передаче ее вызывающей f() функции.
Бывает случаи, когда окончание программы при появлении
неожиданной особой ситуации является слишком строгим решением.
Допустим функция g() написана для несетевого режима в распределенной
системе. Естественно, в g() ничего неизвестно об особых ситуациях,
связанных с сетью, поэтому при появлении любой из них вызывается
unexpected(). Значит для использования g() в распределенной системе
нужно предоставить обработчик сетевых особых ситуаций или переписать
g(). Если допустить, что переписать g() невозможно или нежелательно,
проблему можно решить, переопределив действие функции unexpected().
Для этого служит функция set_unexpected(). Вначале мы определим
класс, который позволит нам применить для функций unexpected()
метод "запроса ресурсов путем инициализации" :
typedef void(*PFV)();
PFV set_unexpected(PFV);
class STC { // класс для сохранения и восстановления
PFV old; // функций unexpected()
public:
STC(PFV f) { old = set_unexpected(f); }
~STC() { set_unexpected(old); }
};
Теперь мы определим функцию, которая должна в нашем примере заменить
unexpected():
void rethrow() { throw; } // перезапуск всех сетевых
// особых ситуаций
Наконец, можно дать вариант функции g(), предназначенный для работы
в сетевом режиме:
void networked_g()
{
STC xx(&rethrow); // теперь unexpected() вызывает rethrow()
g();
}
В предыдущем разделе было показано, что unexpected() потенциально
вызывается из обработчика catch (...). Значит в нашем случае
обязательно произойдет повторный запуск особой ситуации. Повторный
запуск, когда особая ситуация не запускалась, приводит к вызову
terminate(). Поскольку обработчик catch (...) находится вне той
области видимости, в которой была запущена сетевая особая ситуация,
бесконечный цикл возникнуть не может.
Есть еще одно, довольно опасное, решение, когда на неожиданную
особую ситуацию просто "закрывают глаза":
void muddle_on() { cerr << "не замечаем особой ситуации\n"; }
// ...
STC xx(&muddle_on); // теперь действие unexpected() сводится
// просто к печати сообщения
Такое переопределение действия unexpected() позволяет нормально
вернуться из функции, обнаружившей неожиданную особую ситуацию.
Несмотря на свою очевидную опасность, это решение используется.
Например, можно "закрыть глаза" на особые ситуации в одной части
системы и отлаживать другие ее части. Такой подход может быть
полезен в процессе отладки и развития системы, перенесенной с языка
программирования без особых ситуаций. Все-таки, как правило лучше,
если ошибки проявляются как можно раньше.
Возможно другое решение, когда вызов unexpected() преобразуется
в запуск особой ситуации Fail (неудача):
void fail() { throw Fail; }
// ...
STC yy(&fail);
При таком решении вызывающая функция не должна подробно разбираться в
возможном результате вызываемой функции: эта функции завершится
либо успешно (т.е. возвратится нормально), либо неудачно (т.е.
запустит Fail). Очевидный недостаток этого решения в том, что
не учитывается дополнительная информация, которая может сопровождать
особую ситуацию. Впрочем, при необходимости ее можно учесть, если
передавать информацию вместе с Fail.