Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

Одно из самых больших преимуществ применения иерархии классов исключений состоит в том, что это позволяет выразить сущность исключительной ситуации с помощью типа самого класса исключения, а не с помощью кода ошибки, текстовой строки, уровня серьезности ошибки или чего-то подобного. Именно так стандартная библиотека определяет стандартные исключения в заголовочном файле

<stdexcept>
. Базовым классом исключений в
<stdexcept>
является
exception
, который фактически определяется в
<exception>
. На рис. 9.1 показана иерархия стандартных классов исключений.

Рис. 9.1.

Иерархия стандартных классов исключений

Название каждого стандартного класса исключений говорит, для каких исключительных ситуаций он предназначен. Например, класс

logic_error
(логическая ошибка) представляет ситуации, которые должны отыскиваться при написании или рецензировании программного кода, и его подклассы представляют следующие ситуации: нарушение предусловия, применение индекса, выходящего за допустимый диапазон, использование недопустимого аргумента и т.п. Дополнением логической ошибки является ошибка времени выполнения, которая представляется классом
runtime_error
. Она предназначена для ситуаций, которые не всегда могут быть выявлены на этапе кодирования программы, например выход за пределы диапазона, переполнение или потеря значимости (underflow).

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

database_error
,
network_error
,
painting_error
и т.п. Мы обсудим это позже. А до этого давайте рассмотрим, как работают стандартные исключения.

Поскольку стандартная библиотека использует стандартные классы исключений (допустим это), можно ожидать, что классы из стандартной библиотеки будут их выбрасывать при возникновении проблемной ситуации, например при попытке использовать индекс, выходящий за пределы диапазона вектора

vector
.

std::vector<int> v;

int i = -1;

// заполнить вектор v...

try {

 i = v.at(v.size); // Выход на один элемент за конец вектора

} catch (std::out_of_range& е) {

 std::cerr << "Whoa, exception thrown: " << e.what << '\n';

}

vector<>::at
выбросит исключение
out_of_range
, если вы используете индекс, значение которого меньше или больше, чем
size - 1
. Поскольку вам это известно, вы можете написать обработчик, специально предназначенный для этой исключительной ситуации. Если вам не требуется обрабатывать отдельно конкретный тип исключения, а вместо этого вы предпочли бы одинаково обрабатывать все исключения, вы можете перехватить базовый класс всех исключений.

catch(std::exception& е) {

 std::cerr << "Nonspecific exception: " << e.what << '\n';

}

В результате будет перехватываться любой класс, производный от

exception
,
what
— это виртуальная функция-член, которая выдает строку сообщения, зависящую от реализации.

Я почти вернулся в исходную точку. Цель примера 9.1, который сопровождается продолжительным обсуждением, — иллюстрация достоинств класса исключения. Две вещи делают класс исключения полезным: иерархия, отражающая природу исключения, и сообщение, выдаваемое при перехвате исключения и предназначенное для пользователей программы. Иерархия классов исключений позволит разработчикам, использующим вашу библиотеку, создавать безопасный программный код и легко его отлаживать, а текст сообщения позволит тем же самым разработчикам предоставлять конечным пользователям приложения осмысленное сообщение об ошибке.

Исключения представляют собой сложную тему, и

безопасная и эффективная обработка исключительных ситуаций является одной из самых трудных задач в проектировании программного обеспечения в целом и на языке C++ в частности. Каким должен быть конструктор, который не приведет к утечке памяти, если исключение выбрасывается в его теле или в списке инициализации? Что такое безопасное исключение? Я отвечу на эти и другие вопросы в последующих рецептах.

9.2. Создание безопасного при исключениях конструктора

Проблема

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

Решение

Используйте в конструкторе блоки

try
и
catch
, чтобы правильно завершить действия по очистке объекта, если в ходе конструирования выбрасывается исключение. В примере 9.2 приводятся простые классы
Device
и
Broker
.
Broker
создает два объекта
Device
в динамической памяти (
heap
), но он должен правильно очистить память от этих объектов, если при конструировании выбрасывается исключение.

Пример 9.2. Безопасный при исключениях конструктор

#include <iostream>

#include <stdexcept>

using namespace std;

class Device {

public:

 Device(int devno) {

if (devno == 2)

throw runtime_error("Big problem");

 }

 ~Device {}

};

class Broker {

public:

 Broker (int devno1, int devno2) : dev1_(NULL), dev2_(NULL) {

try {

dev1_ = new Device(devno1); // Заключить операторы создания

dev2_ = new Device(devno2); // объектов в динамической памяти в

// блок try ...

} catch (...) {

delete dev1_; // ...очистить память и повторно

throw; // выбросить исключение, если что-то не

// получилось.

}

 }

 ~Broker {

delete dev1_;

delete dev2_;

 }

private:

 Broker;

 Device* dev1_;

 Device* dev2_;

};

int main {

 try {

Broker b(1, 2);

 } catch(exception& e) {

cerr << "Exception: " << e.what << endl;

 }

}

Обсуждение

Сказать, что конструктор, функция-член, деструктор или что-нибудь другое «безопасно при исключениях», — значит гарантировать, что при их работе не будет утечки ресурсов и, вероятно, используемые ими объекты не будут находиться в противоречивом состоянии. В языке C++ такого рода гарантии названы базовыми и строгими.

Поделиться с друзьями: