CHAP8_3 (1018811), страница 4

Файл №1018811 CHAP8_3 (Сборник литературы - С и С++) 4 страницаCHAP8_3 (1018811) страница 42017-07-08СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 4)

Листинг 15 показывает простую систему определений класса для возбуждения исключений. Я могу перехватить ошибки чтения или записи подобным образом:

try

{

file f("name", "rw");

buffer b;

b = f.read() f.write( b );

}

catch( file::open_error &r )

{

// Файл не существует или не может быть открыт.

}

catch( file::io_error &r )

{

// Какая-то из неисправимых ошибок ввода/вывода.

}

Если меня волнует лишь то, что произошла ошибка определенного вида, и не волнует, какого конкретно, то я могу сделать так:

file f;

try

{

buffer b;

b = f.read()

f.write( b );

}

catch( file::error &r )

{

// ...

}

Листинг 15. Классы исключений

  1. class file

  2. {

  3. public:

  4. class error {};

  5. class open_error : public error {};

  6. class io_error : public error {};

  7. // ...

  8. }

Этот код работает, потому что объект file::read_error является объектом типа file::error (так как относится к производному классу). Вы всегда можете перехватить объект производного класса, используя ссылку или указатель базового класса.

Я мог бы также предложить другой класс, использующий тот же самый механизм:

class long_double

{

public:

class error {};

class didvide_by_zero : public error {};

// ...

};

Так как классы error являются вложенными определениями, то именами на самом деле являются file::error и long_double::error, поэтому здесь нет конфликта имен.

Для упрощения сопровождения я всегда использую error в качестве своего базового класса для исключений. (Я не мог использовать производный класс, даже если здесь был бы возможен всего один вид ошибки). Таким образом, я знаю, что имея возбуждающий исключение класс some_class, можно перехватить это исключение при помощи:

catch(some_class::error &r)

Эту ошибку искать не придется. Если применяется наследование, то я использую базовый класс error таким образом:

class employee

{

public:

class error {};

class database_access_error : public error {};

};

class peon : public employee

{

class error : public employee::error {};

class aagh : public error {};

};

Этим способом исключение aagh может быть перехвачено как peon::aagh, peon::error или employee::error.

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

161. Возбуждение исключений из конструктора ненадежно.

Я начну этот раздел с замечания о том, что компиляторы, которые соответствуют рабочим документам комитета ISO/ANSI по С++, не имеют большей части из рассматриваемых здесь проблем. Тем не менее, многие компиляторы (один из которых компилятор Microsoft) им не соответствуют.

Ошибки в конструкторах являются действительной проблемой С++. Так как они не вызываются явно, то и не могут возвратить коды ошибок обычным путем. Задание для конструируемого объекта "неверного" значения в лучшем случае громоздко и иногда невозможно. Возбуждение исключения может быть здесь решением, но при этом нужно учесть множество вопросов. Рассмотрим следующий код:

class c

{

class error {};

int *pi;

public:

c() { throw error(); }

// ...

};

void f( void )

{

try

{

c *cp = new c; // cp не инициализируется, если не выполняется

// ... // конструктор,

delete cp; // эта строка в любом случае не выполнится.

}

catch( c::error &err )

{

printf ("Сбой конструктора\n");

delete cp; // Дефект: cp содержит теперь мусор

}

}

Проблема состоит в том, что память, выделенная оператором new, никогда не освобождается. То есть, компилятор сначала выделяет память, затем вызывает конструктор, который возбуждает объект error. Затем управление передается прямо из конструктора в catch-блок. Код, которым возвращаемое значение оператора new присваивается cp, никогда не выполняется - управление просто перескакивает через него. Следовательно, отсутствует возможность освобождения памяти, потому что у вас нет соответствующего указателя. Чтение мной рабочих документов комитета ISO/ANSI по С++ показало, что такое поведение некорректно - память должна освобождаться неявно. Тем не менее, многие компиляторы делают это неправильно.

Вот простой способ исправить эту сложную ситуацию (я поместил тело функции в определение класса лишь для того, чтобы сделать пример покороче):

class с

{

int *pi;

public:

c() { /*...*/ throw this; }

};

void f( void )

{

try

{

c *cp = NULL;

cp = new c;

c a_c_object();

}

catch( c *points_at_unconstructed_object )

{

if( !cp ) // если конструктор, вызванный посредством 'new', не выполняется

delete points_at_unconstructed_object;

}

}

Ситуация усложняется, когда некоторые объекты размещаются при помощи new, а другие - из динамической памяти. Вы должны сделать что-то похожее на следующее, чтобы понять, в чем дело:

void f( void )

{

c *cp = NULL; // cp должен быть объявлен снаружи try-блока, потому что

// try-блок образует область действия, поэтому cp не может

// быть доступным в catch-блоке будучи объявлен в try-блоке.

try

{

c a_c_object;

cp = new c;

}

catch( c *points_at_unconstructed_object )

{

if( !cp ) // если конструктор, вызванный посредством 'new', не выполняется

delete points_at_unconstructed_object;

}

}

Вы не можете решить эту проблему внутри конструктора, потому что для конструктора нет возможности узнать, получена ли инициализируемая им память от new, или из стека.

Во всех предыдущих примерах деструктор для сбойных объектов вызывается, даже если конструктор не выполнился и возбудил исключение. (Он вызывается или косвенно посредством оператора delete, или неявно при выходе объекта из области действия, даже если он покидает ее из-за возбуждения исключения).

Аналогично, вызов delete косвенно вызывает деструктор для этого объекта. Я сейчас вернусь к этой ситуации. Перед выходом из этого деструктора незавершенный конструктор должен привести объект в исходное состояние перед тем, как сможет возбудить ошибку. С учетом предшествующего определения класса c следующий код будет работать при условии, что отсутствует ошибка до оператора new int[128] и new выполнен успешно:

c::c( )

{

if( some_error() )

throw error(this); // ДЕФЕКТ: pi неинициализирован.

// ...

pi = new int[128]; // ДЕФЕКТ: pi неинициализирован, если

// ... // оператор new возбуждает исключение.

if( some_other_error() )

{

delete [] pi; // Не забудьте сделать это.

throw error(this); // Это возбуждение безопасно.

}

}

c::~c( )

{

delete pi;

}

Запомните, что pi содержит мусор до своей инициализации оператором new. Если возбуждается исключение до вызова new или сам оператор new возбудит исключение, то тогда pi никогда не инициализируется. (Вероятно, оно не будет содержать NULL, а будет просто неинициализированно). Когда вызывается деструктор, то оператору delete передается это неопределенное значение. Решим проблему, инициализировав этот указатель безопасным значением до того, как что-то испортится:

c::c( ) : pi(NULL) // инициализируется на случай, если оператор 'new' даст сбой

{

if( some_error() )

throw error(this); // Это возбуждение теперь безопасно.

// ...

pi = new int[128]; // Сбой оператора new теперь безопасен.

// ...

if( some_other_error() )

{

delete [] pi; // Не забудьте высвободить динамическую память.

throw error(this); // Это возбуждение безопасно.

}

}

c::~c( )

{

if( pi )

delete pi;

}

Вы должны запомнить, что нужно освободить успешно выделенную память, если исключение возбуждается после операции выделения, так, как было сделано ранее.

У вас есть возможность почистить предложенный выше код при его использовании с учетом моего совета из предыдущего правила о возбуждении исключения объекта error и скрытия всех сложностей в этом объекте. Однако определение этого класса получается значительно более сложным. Реализация в листинге 16 опирается на том факт, что деструктор явно объявленного объекта должен вызываться при выходе из try-блока, перед выполнением catch-блока. Деструктор для объекта, полученного при помощи new, не будет вызван до тех пор, пока память не будет передана оператору delete, что происходит в сообщении destroy(), посланном из оператора catch. Следовательно, переменная has_been_destroyed будет означать истину, если объект получен не при помощи new и исключение возбуждено из конструктора, и ложь - если объект получен посредством new, потому что деструктор еще не вызван.

Конечно, вы можете вполне резонно заметить, что у меня нет причин проверять содержимое объекта, который по теории должен быть уничтожен. Здесь уже другая проблема. Некоторые компиляторы (в том числе компилятор Visual C++ 2.2 Microsoft) вызывают деструктор после выполнения оператора catch, даже если объекты, определенные в try-блоке, недоступны из catch-блока. Следовательно, код из листинга 16 не будет работать с этими компиляторами. Вероятно, лучшим решением состояло бы в написании варианта operator new(), который мог бы надежно указывать, получена память из кучи, или из стека.

Листинг 16. except.cpp - возбуждение исключения из конструктора

  1. class с

  2. {

  3. public:

  4. class error

  5. {

  6. c *p; // NULL при успешном выполнении конструктора

  7. public:

  8. error( c *p_this );

  9. void destroy( void );

  10. };

  11. private:

  12. unsigned has_been_destroyed : 1;

  13. int *pi;

  14. private: friend class error;

  15. int been_destroyed( void );

  16. public:

  17. c() ;

  18. ~c();

  19. };

  20. //=============================================================

  21. c::error::error( c *p_this )

  22. : p( p_this )

  23. {}

  24. //----------------------------------------------------------------------------------------------------------------

  25. void c::error::destroy( void )

  26. {

  27. if( p && !p->been_destroyed() )

  28. delete p;

  29. }

  30. //=============================================================

  31. c::c() : has_been_destroyed( 0 )

  32. {

  33. // ...

  34. throw error(this);

  35. // ...

  36. }

  37. //----------------------------------------------------------------------------------------------------------------

  38. c::~c()

  39. {

  40. // ...

  41. has_beeb_destroyed = 1;

  42. }

  43. //--------------------------------------------------------------

  44. int c::been_destroyed( void )

  45. {

  46. return has_been_destroyed;

  47. }

  48. //===============================================================

  49. void main( void )

  50. {

  51. try

  52. {

  53. c *cp = new c;

  54. c a_c_object;

  55. delete cp;

  56. }

  57. catch( c::error &err )

  58. {

  59. err.destroy(); // деструктор вызывается, только если объект создан оператором new

  60. }

  61. }

Заключение

Так-что вот. Множество правил, которые я считаю полезными и которые, надеюсь, будут полезны и для вас. Конечно, многие из представленных здесь правил дискуссионны. Пожалуйста, я готов с вами о них поспорить. Несомненно, я не считаю себя каким-то законодателем в стиле С++ и сам нарушаю многие из этих правил при случае, но я искренне верю, что следование этим правилам сделает меня лучшим программистом, и надеюсь, что вы их тоже оцените.

Я закончу вопросом. Сколько времени потребуется программисту на С++ для того, чтобы заменить электрическую лампочку? Ответ - нисколько, а вы, кажется, все еще мыслите процедурно. Правильно спроектированный класс электрическая_лампа должен наследовать метод замены от базового класса лампа. Просто создайте объект производного класса и пошлите ему сообщение заменить_себя.

Об авторе

Ален Голуб - программист, консультант и преподаватель, специализирующийся на С++, объектно-ориентированном проектировании и операционных системах Microsoft. Он проводит семинары по приглашению частных фирм повсюду на территории США и преподает в филиалах Калифорнийского университета, расположенных в Беркли и Санта-Круз. Он также работает программистом и консультантом по объектно-ориентированному проектированию, используя С и С++ в операционных средах Micrisoft Windows, Windows 95, Windows NT и UNIX.

Характеристики

Тип файла
Документ
Размер
70,5 Kb
Тип материала
Высшее учебное заведение

Список файлов книги

С и С++ - сборник литературы
C++ Бархатный путь - Марченко А
cpp_001.shtml
cpp_002.shtml
cpp_003.shtml
cpp_004.shtml
cpp_005.shtml
cpp_006.shtml
cpp_007.shtml
cpp_008.shtml
cpp_009.shtml
cpp_010.shtml
cpp_011.shtml
cpp_012.shtml
cpp_013.shtml
cpp_014.shtml
cpp_015.shtml
cpp_016.shtml
cpp_017.shtml
cpp_018.shtml
cpp_019.shtml
cpp_020.shtml
cpp_021.shtml
cpp_022.shtml
cpp_023.shtml
cpp_024.shtml
cpp_025.shtml
cpp_026.shtml
cpp_027.shtml
cpp_030.shtml
cpp_034.shtml
Свежие статьи
Популярно сейчас
Почему делать на заказ в разы дороже, чем купить готовую учебную работу на СтудИзбе? Наши учебные работы продаются каждый год, тогда как большинство заказов выполняются с нуля. Найдите подходящий учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
7029
Авторов
на СтудИзбе
260
Средний доход
с одного платного файла
Обучение Подробнее