Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
програмирование 17-20.docx
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
82.57 Кб
Скачать

Void use_file( const char* filename )

{

FilePointer f( filename, “w” );

// используем f

}

и деструктор будет вызван независимо от того, завершается функция

нормально или возникает исключительная ситуация.

Описанная выше техника управления ресурсами путем инициализации

переменных является достаточно общим приемом, основанным на свойствах

конструкторов и деструкторов и их взаимодействием с системой обработки

исключений в С++. Объект не считается созданным, пока не выполнен его

конструктор. Только в этом случае последующее удаление объекта из стека

вызовет его деструктор. Объект, поля которого в свою очередь являются

20

объектами, считается созданным только после создания всех его полей.

Создавая объект, правильно написанный конструктор при неудаче должен

(насколько это возможно) восстановить состояние системы и не оставлять

«полусозданный» объект. Это можно обеспечить путем управления ресурсами

через инициализацию.

Представьте себе класс X, конструктор которого должен получить два

ресурса: файл file и его блокировку lock. Конструктор может и не получить

какой-либо из требуемых ресурсов; в этом случае он возбуждает исключение.

Чтобы не усложнять жизни программисту, конструктор класса X не должен

нормально завершаться, открыв файл, но не получив его блокировки. Можно

использовать два класса FilePointer и LockPointer для представления полученных

ресурсов (если ресурсы одного типа, то можно ограничиться одним классом).

Выделение ресурса реализуется путем инициализации локального объекта,

представляющего ресурс:

class X {

private:

FilePointer a;

LockPointer b;

// . . . . .

public:

X( const char* aa, const char* bb )

: a( aa ), // выделение 1-го ресурса

b( bb ) // выделение 2-го ресурса

{ }

// . . . .

};

Теперь при создании локального объекта реализация класса позаботится обо

всей «бухгалтерии». Например, исключение возникло при создании a, но еще

до создания b. В этом случае будет вызван деструктор для переменной a, но не

для b. Это означает, что если мы используем рассмотренную простую модель

управления ресурсами, то все будет нормально и, что особенно важно, автору

конструктора не нужно писать код для обработки исключений.

Наиболее часто запрашиваемый ресурс это динамическая память.

Обычно выделение памяти выглядит так:

class X {

private:

int* p;

21

// . . . . .

public:

X( int s ) { p = new int[s]; init( ); }

~X( ) { delete p; }

// . . . . .

};

Однако такая техника при использовании совместно с исключениями может

привести к «утечке» памяти. В частности, если исключение заявлено функцией

Init(), то выделенная память не будет освобождена, поскольку объект полностью

не создан и деструктор для него вызван не будет. Более безопасный вариант

может выглядеть следующим образом:

template<class T> class MemPtr {

private:

T* p;

public:

MemPtr( unsigned s ) { p = new T[s]; }

~MemPtr( ) { delete p; }

operator T*( ) { return p; }

};

class X {

private:

MemPtr<int> ip;

// . . . . .

public:

X( int s ) : ip( s ) { init( ); }

};

Освобождение памяти, занятой под массив, на который указывает ip, теперь

выполняется в MemPtr. Теперь, если функция init() заявит исключение, память

будет освобождена при (неявном) вызове деструктора для объекта ip. Заметим

также, что правила распределения памяти С++ гарантируют, что конструктор

класса X не будет вызван, если для объекта ip не выделено памяти. Таким

образом, не надо беспокоиться, что для несуществующего объекта может быть

вызван деструктор.

22

СПЕЦИФИКАЦИЯ ИНТЕРФЕЙСА ФУНКЦИИ, ЗАЯВЛЯЮЩЕЙ

ИСКЛЮЧЕНИЯ

Возможность возбуждения и обработки исключений функцией должна

учитываться при программировании взаимодействия этой функции с другими

программными модулями и в первую очередь, вызывающими ее функциями. В

языке С++ заголовок функции включает спецификацию исключений, которые

данная функция может генерировать: