
- •18.Классы для работы с векторами и матрицами.
- •1.1 Описание программы, матрицы
- •1.3 Microsoft Visual Studio Express
- •1.4 Стандартная библиотека шаблонов (stl)
- •1.5 Vector
- •1.6 Перегрузка операторов
- •2. Проектирование и этапы разработки
- •2.1 Постановка задачи
- •Int lbound;
- •Int ubound;
- •1) Создается и запоминается копия объекта (переменной). Это означает, что
- •2) Разматывает стек, вызывая деструкторы локальных объектов, выходящих
- •3) Передается управление ближайшему catch-обработчику, совместимому с
- •Void f1(void) {
- •Void f2( Vector& V ) {
- •Void f() {
- •Void g() {
- •Void use_file( const char* filename ) {
- •Void use_file( const char* filename ) {
- •Void get_resources( ) {
- •Void use_file( const char* filename )
- •X( const char* aa, const char* bb )
- •Init(), то выделенная память не будет освобождена, поскольку объект полностью
- •X( int s ) : ip( s ) { init( ); }
- •Void f( int a ) throw( Range, Size, int, char* )
- •Void f( int a ) {
- •Int g( void ) throw( ); // функция не заявляет каких-либо исключений
- •Void f( void ) throw( int ){ throw “This error message has type char* ”; }
- •Void rethrow( ) { throw; }
- •Void network_g( )
Void f() {
try {
// . . . . .
}
catch( network_err ) {
// . . . . .
}
}
так и функциями, обрабатывающими исключения файловой системы:
Void g() {
try {
// . . . . .
}
catch( file_system_err ) {
// . . . . .
}
}
Это важно, так как сервис (например, сетевой) может быть прозрачен
настолько, что программист, пишущий g() даже не будет подозревать о
существовании сети.
Заметим, что при организации иерархии нет необходимости иметь общий
базовый класс для всех исключений, поскольку имеется возможность перехвата
всех исключений обработчиком по умолчанию. Однако если используется
общий базовый класс для всех исключительных ситуаций, то необходимо
сделать его пустым, за исключением виртуального деструктора, и строго
придерживаться принципов наследования.
РАСПРЕДЕЛЕНИЕ РЕСУРСОВ ПРИ ИСПОЛЬЗОВАНИИ ИСКЛЮЧЕНИЙ
Когда требуется использовать некоторый ресурс, например, динамически
распределить память или получить доступ к файлу, то важно после захвата и
использования этого ресурса его освободить. Обычная ситуация такова, что
ресурсы распределяются в начале работы некоторой функции и освобождаются
перед ее завершением:
Void use_file( const char* filename ) {
FILE* f = fopen( filename, “w” );
18
// используем f
fclose( f );
}
Однако подобная последовательность действий требует корректировки, когда
мы предполагаем обработку исключений. Действительно, если после вызова
fopen(), но перед обращением к fclose(), в функции use_file() возникает
исключительная ситуация, то мы покидаем функцию use_file() не закрыв файла.
Можно рассмотреть такое, например, решение:
Void use_file( const char* filename ) {
FILE* f = fopen( filename, “w” );
try {
// используем f
}
catch( . . . ) {
fclose( f );
throw;
}
fclose( f );
}
Здесь код, работающий с файлом, целиком заключен в try-блок, у которого
обработчик по умолчанию при возникновении исключения закрывает файл и
повторно заявляет то же самое исключение. Однако такое решение выглядит не
элегантно. В чем же состоит правильный подход?
Заметим, что общая схема действий выглядит так:
Void get_resources( ) {
// выделить ресурс 1
// . . . . .
// выделить ресурс N
// использование ресурсов
// освободить ресурс N
// . . . . .
// освободить ресурс 1
}
19
причем, часто важно освобождать ресурсы в порядке, обратном их выделению.
Это очень напоминает создание автоматических локальных переменных,
размещение которых в стеке сопровождается вызовом конструктора, а
уничтожение – деструктора. Возникает идея решить проблемы выделения и
освобождения ресурсов, используя объекты классов с конструкторами и
деструкторами, причем в конструкторе ресурс выделяется, а в деструкторе
освобождается. Использование автоматических локальных переменных
обеспечит корректное управление ресурсами, в том числе и при исключениях.
Например, для работы с файлами можно определить класс FilePointer,
работающий как FILE*:
class FilePointer {
private:
FILE* fptr;
public:
FilePointer( const char* filename, const char* attr )
{ fptr = fopen( filename, attr ); }
FilePointer( FILE* pf ) { fptr = pf; }
~FilePointer( ) { fclose( fptr ); }
operator FILE*( ) { return fptr; }
};
Переменную типа FilePointer мы можем создавать, имея либо указатель FILE*,
либо параметры для функции fopen(). В любом случае, автоматическая
локальная переменная типа FilePointer будет уничтожена при выходе из области
видимости, и ее деструктор закроет файл. Функция use_file() сокращается до
минимума: