2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп
.pdf
|
|
|
|
|
3.5. |
Обработка ошибок |
|
void |
user(int sz) |
noexcept |
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
Vector v(sz); |
|
|
|
|
|
|
|
iota(&v[O],&v[sz],1); |
//Заполняет |
v |
значениями |
1,2,3,4." |
(§14.3) |
|
|
11 ... |
|
|
|
|
|
|
61
Если
все благие
намерения
остались
лишь
намерениями
и
функция
user
()
по-прежнему
генерирует
исключение,
вызывается
функция
std::
termi
na
te
()
,
которая
немедленно
прекращает
выполнение
программы.
3.5.2.
Инварианты
Использование
исключений
для
сигнализации
о
выходе
за
границы
масси
ва
является
примером
функции,
проверяющей
свои
аргументы
и
отказываю
щейся
работать,
когда
не
выполняется
ее
основное
предусловие.
Если
бы
мы
формально
описывали
оператор
индекса
Vector,
мы
бы
указали
что-то
на
подобие
"индекс
должен
быть
в
диапазоне
[О:
size
()
)
",и
это
фактически
и
есть то
условие,
которое
мы
проверяем
в
нашей
реализации
opera
tor
[] ().
Обозначение |
[а : Ь) |
означает |
полуоткрытый диапазон, в котором а |
является |
||
частью диапазона, а |
Ь - |
нет. |
Всякий раз, определяя функцию, |
мы |
должны |
|
рассмотреть, |
каковы |
ее предусловия и следует ли их тестировать |
(§3.5.3). Для |
|||
большинства |
приложений |
проверка простых инвариантов является |
хорошей |
идеей; см. |
также §3.5.4. |
Однако |
operator [] |
()
работает
с
объектами
типа
Vector,
и
ничто
из
того,
что
он
делает,
не
имеет
смысла,
пока
элементы
Vector
не
будут
иметь
"разумные"
значения.
В
частности,
мы
говорили,
что
"elem
указывает
на
массив
из
sz
элементов
типа
douЫe'',
но
это
было
сказано
только
в
коммен
тарии.
Такое
утверждение
о
том,
что
предполагается
истинным
для
класса,
называется
инвариантом
класса
или
просто
инвариантом.
Задача
конструк
тора
-
установить
инвариант
своего
класса
(так,
чтобы
функции-члены мог
ли
полагаться
на то,
что
он
выполняется),
а
задача
функций-членов
-
гаран
тировать
выполнение
инварианта
после
их
завершения.
К
сожалению,
наш
конструктор
Vector
только
частично
выполнил
свою
работу.
Он
правильно
инициализировал
элементы
Vector,
но
не
убедился,
что
переданные
ему
ар
гументы
имели
смысл.
Что
будет
при
следующем
вызове?
Vector
v(-27);
Похоже,
это
приведет
к
неприятностям.
Вот
более
надежное
определение:
Vector::Vector(int s) |
||
{ |
|
|
if |
(s<O) |
|
|
throw |
length_error{"Vector: |
отрицательный
размер"};
62
|
Глава 3. Модульность |
|||
elem |
= |
new |
douЬle[s]; |
|
sz |
= |
s; |
|
|
Для
сообщения
о
некорректном
числе
элементов
я
использую
исключение
length
_
error
стандартной
библиотеки,
потому
что
некоторые
операции
стандартной
библиотеки
используют
его
для
сообщения
о
подобных
пробле
мах.
Если
оператор
new
не
может
выделить
необходимую
память,
он
генери
рует
исключение
std:
:bad_alloc.
Теперь
мы
можем
написать:
void {
test
(
1
try
Vector v(-27);
catch
//
(std::length |
- |
error& |
err) |
|
Обработка |
отрицательного |
(
размера
catch |
(std::bad_alloc& err) |
// |
Обработка исчерпания |
( памяти
Вы
можете
определить
собственные
классы,
которые
будут
использоваться
в
качестве
исключений,
и
заставить
их
переносить
любую
информацию
из
точки,
где
обнаружена
ошибка,
в
точку,
в
которой
она
может
быть
обработана
(§3.5.1).
Часто
функция
не
имеет
возможности
завершить
назначенную
ей
задачу
после
генерации
исключения.
В
таком
случае
"обработка"
исключения
озна
чает
выполнение
минимальной
локальной
очистки
и
повторную
генерацию
того
же
исключения.
Например:
void
test
()
(
try Vector v(-27);
catch
(std:
:length
error&)
(
//Некоторые 11 повторная
действия генерация
и
исключения
cerr <<"Сбой throw;
test():
ошибка |
размера вектора\n"; |
// |
Повторная генерация |
catch |
(std::bad_alloc&) ( |
std: :terminate(); |
//Наша программа |
не в |
состоянии об |
|
// работать ошибку |
исчерпания |
памяти |
|
//Завершение программы |
|
|
64
Глава
3.
Модульность
• |
Ожидается, что непосредственная |
вызывающая |
|
обработать такую ошибку. |
|
Исключения генерируются в следующих |
ситуациях. |
функция
в
состоянии
•
•
•
•
•
•
•
Ошибка |
настолько редкая, что |
программист, |
скорее всего, забудет |
вы |
||||||||||||||||
полнить |
ее проверку. |
Например, |
когда |
вы в |
последний |
раз проверяли |
||||||||||||||
значение, возвращаемое |
функцией printf ()? |
|
|
|
|
|
|
|||||||||||||
Ошибка |
не может быть |
обработана |
непосредственной |
вызывающей |
||||||||||||||||
функцией. Вместо этого |
ошибка должна быть передана конечной вызы |
|||||||||||||||||||
вающей |
функции. |
Например, невозможно, чтобы каждая |
функция |
при |
||||||||||||||||
ложения надежно |
обрабатывала все |
ошибки |
выделения памяти или об |
|||||||||||||||||
рыва соединения сети. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
В модулях более |
низкого уровня |
в |
приложении |
могут |
быть добавле |
|||||||||||||||
ны новые типы |
ошибок, |
|
при том |
что |
модули |
более |
высокого уровня не |
|||||||||||||
были написаны |
с |
учетом |
необходимости обработки таких ошибок |
(на |
||||||||||||||||
пример, |
если бывшее |
однопоточным |
приложение |
было |
изменено |
для |
||||||||||||||
работы с несколькими |
потоками или |
с удаленными |
ресурсами с досту |
|||||||||||||||||
пом по сети). |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Отсутствует |
подходящий путь |
возврата для |
кодов |
ошибок. Например, |
||||||||||||||||
конструктор |
не имеет |
возвращаемого |
значения, |
которое |
могла бы |
про |
||||||||||||||
верить вызывающая функция |
1 |
• Кроме того, |
конструкторы могут |
быть |
||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
вызваны |
для |
нескольких |
локальных |
переменных |
или в частично |
скон |
||||||||||||||
струированном |
сложном |
объекте, |
так что очистка |
на основе кодов |
оши |
|||||||||||||||
бок окажется |
довольно сложной. |
|
|
|
|
|
|
|
|
|
|
|
||||||||
Путь возврата значения из функции оказывается |
более сложным или до |
|||||||||||||||||||
рогостоящим |
из-за необходимости возвращать |
как |
значение, так |
и |
ин |
|||||||||||||||
дикатор |
ошибки (например, в виде pair; §13.4.3), |
что может привести к |
||||||||||||||||||
использованию |
лишних |
параметров |
и нелокальных указателей ошибки |
|||||||||||||||||
или к другим |
обходным |
путям. |
|
|
|
|
|
|
|
|
|
|
|
|
||||||
Ошибка |
должна быть |
передана цепочке вызовов |
"конечной вызываю |
|||||||||||||||||
щей функции". |
Многократная |
|
проверка |
кода ошибки утомительна, |
до |
|||||||||||||||
рогостояща и чревата ошибками. |
|
|
|
|
|
|
|
|
|
|
|
|||||||||
Восстановление после ошибки зависит |
от результатов нескольких |
вы |
||||||||||||||||||
зовов функций, |
что ведет к необходимости поддерживать локальное со |
|||||||||||||||||||
стояние |
между |
вызовами |
и к усложненным управляющим структурам. |
1 |
Возможность возврата кода ошибки из конструктора через параметр-ссылку (или |
|
тель), а также через глобальную переменную сути дела не меняет. - |
Примеч. ред. |
указа
3.5.
Обработка
ошибок
65
•
•
Функция, обнаружившая ошибку, |
является функцией обратного вызова |
(функциональный аргумент), так |
что непосредственная вызывающая |
функция может даже не знать, что |
функция была вызвана. |
Ошибка требует выполнения некоторой "отмены действий". |
Программа
завершается
в
следующих
случаях.
•
•
Восстановление |
после ошибки данного вида невозможно. Например, во |
||
многих - |
но не |
во всех - |
системах нет разумного способа восстанов |
ления после исчерпания доступной памяти. |
|||
В данной системе обработка нетривиальных ошибок требует перезапу- |
|||
ска потока, процесса или |
компьютера. |
Одним
из
способов
гарантировать
завершение
работы
приложения
являет
ся
добавление
noexcept
к
функции,
с
тем
чтобы
генерация
исключения
с
помощью
throw
в
любом
месте
реализации
функции
приводила
к
вызову
terminate
().
Обратите
внимание,
что
существуют
приложения,
для
кото
рых
безусловное
завершение
работы
неприемлемо,
поэтому
необходимо
ис
пользовать альтернативные варианты.
К сожалению, перечисленные ситуации
не
всегда
оказываются
логически
непересекающимися
и
простыми
в
применении.
Имеют
значение
такие
фак
торы,
как
размер
и
сложность
программы.
Иногда
по
мере развития
прило
жения
происходит
изменение
приоритетов
и
компромиссов.
Для
принятия
верного
решения
требуется
опыт.
Если
у
вас
есть
сомнения,
предпочитайте
исключения,
потому
что
их
использование
лучше
масштабируется
и
не
тре
бует
внешних
инструментов
для
проверки
того,
что
все
ошибки
обработаны.
Не
верьте,
что
все
коды
ошибок
или
все
исключения
плохи;
для
каждого
из
механизмов
есть
своя
достаточно
точно
определенная
ниша.
Кроме
того,
не
верьте
мифу
о
том,
что
обработка
исключений
плохо
влияет
на
произво
дительность;
зачастую
она
быстрее,
чем
корректная
обработка
сложных
или
редких условий ошибки, а также повторных проверок кодов ошибок. Идиома RAII (§4.2.2, §5.3) имеет жизненное значение для простой
и
эф
фективной обработки |
ошибок с |
использованием |
ненный trу-блоками, |
зачастую |
просто является |
исключений. Код, перепол отражением худших аспек
тов стратегий
обработки
ошибок
на
основе
кодов
ошибок.
3.5.4.
Контракты
В
настоящее
время
нет
общего
и
стандартного
способа
написания
допол
нительных
тестов
времени
выполнения
для
инвариантов,
предусловий
и
т.п.
Для
включения
в
С++20
предложен
механизм
контракта
[20, 21].
Его
цель
состоит
в
том,
чтобы
поддержать
пользователей,
которые
для
обеспечения
корректной
работы
программы
хотели
бы
положиться
на
тестирование
с
дли-
|
|
|
3.6. Аргументы и |
возвращаемые значения функций |
||||
constexpr douЫe С= |
299792.458; |
11 |
км/с |
|
|
|||
void |
f(douЫe |
speed) |
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
11 160 км/ч == 160.0/(60*60) |
км/с |
|
|
|
|||
|
constexpr |
douЫe |
local_max = |
160.0/(60*60); |
|
|
||
|
11 Ошибка: |
speed |
должна |
быть |
константой |
|
|
|
|
static_assert(speed<C, |
"Такая |
скорость запрещена физикой"); |
|||||
|
static_assert(local_max |
<С, |
"Недостижимая |
скорость"); |
// ОК |
|||
|
// ... |
|
|
|
|
|
|
|
67
В
общем
случае
static_assert
(А, S}
выводит
сообщение
S
в
виде
сооб
щения
компилятора
об
ошибке,
если
условие
А
ложно.
Если
вы
не
хотите
вы
водить
специальный
текст
сообщения
об
ошибке,
уберите S -
и
компилятор
выведет
сообщение
по
умолчанию:
static_assert(4<=sizeof(int));
//Выводит
сообщение
по
умолчанию
Сообщение
по
умолчанию
обычно
содержит
информацию
о
местоположении
static_assert
в
исходном
тексте
и
символьное
представление
проверяемо
го
предиката.
Наиболее
важное
применение
static_assert
-
когда
мы
делаем
те
или
иные
предположения
о типах,
используемых
в
обобщенном
программирова
нии
(§7.2, §13.9).
3.6.
Арrументы и возвращаемые
значения функций
Основной
и
рекомендуемый
способ
передачи
информации
из
одной
части
программы
в
другую
-
через
вызов
функции.
Информация,
необходимая
для
выполнения
задачи,
передается
в
качестве
аргументов
функции,
а
получен
ные результаты
передаются
обратно
как
возвращаемые
значения.
Например:
int
sum(const
vector<int>&
v)
int |
s |
= О; |
|
for |
(const |
int |
|
|
s |
+= i; |
|
return |
s; |
|
i |
v) |
vector int х
=
fib = |
(1,2,3,5,8,13,21); |
|
sum(fib); |
// х становится |
равным
53
Существуют
и
другие
пути
передачи
информации
между
функциями,
та
кие
как
глобальные
переменные(§
1.5),
указатели
и
ссылки
в
качестве
параме-
68
Глава
3.
Модульность
тров
(§3.6.1)
и
совместно
используемое
состояние
в
объекте
класса
(глава
4,
"Классы").
Глобальные
переменные
крайне
не
рекомендуются
к
применению
как
известный
источник
ошибок,
а
разделяемое
состояние
обычно
должно
ис
пользоваться
только
функциями,
совместно
реализующими
точно
определен
ную абстракцию
(например,
функциями-членами
класса,
§2.3).
Учитывая важность передачи информации в функции и из них, неудиви тельно, что имеется множество способов ее осуществления. Ключевыми во
просами
при
этом
являются
следующие.
• • •
Объект копируется или совместно используется? |
|
|
|||
Если |
объект |
совместно используется, |
является ли он изменяемым? |
||
Если |
объект |
перемещается, остается |
ли после |
этого |
"пустой объект" |
(§5.2.2)? |
|
|
|
|
Поведение по умолчанию для передачи аргументов в функцию |
и |
возврата |
|
значений из функций - |
"копирование" (§ 1.9), но некоторые копирования мо |
||
гут быть неявно оптимизированы и превращены в перемещения. |
|
|
В
примере
sum ()
результирующий
int
копируется
из
sum
(),
но
копиро
вать
потенциально
очень
большой
вектор
в
sum
()
было
бы
неэффективно