- •2 Часть
- •1 Приближение
- •1.1 Бросание(генерация) исключений
- •1.2 Пример 1. Поддержка инкапсуляции
- •1.3 Ловля исключений
- •2 Приближение
- •2.1 Развитие способов обработки ошибок
- •1. Хочется, чтобы приложение как можно быстрее упало и не успело сделать ничего плохого.
- •2. Хочется, чтобы перед своей кончиной приложение напечатало нечто, позволяющее понять, что не так.
- •2.2 Преимущества использования исключений
- •2.4 Пример 2. Исключение и память, проброс
- •3.3 Отладка и исключения в Visual Studio. Stack trace
- •3.4 Как развивался концепт с исключениями дальше?
- •3.4.2 Сборщик мусора
- •3.5 Общие замечания по созданию исключительных ситуаций
- •3.6 Код всегда должен писаться, чтобы быть устойчивым к исключениям [1]
2.2 Преимущества использования исключений
Не нужно писать код по обработке ошибок.
Код алгоритма не загромождается кодом по обработке ошибок: его легко понимать и сопровождать.
Исключения не проигнорируешь в отличие от кодов ошибок.
Исключения легко получать из глубоко вложенных функций. Не нужно менять интерфейс функций, чтобы они возвращали какой-то код, достаточно обернуть вызов в try catch.
2.3 Какие есть проблемы с исключениями в С++
Утечки ресурсов и памяти. В С++ нет автоматического сборщика мусора. Поэтому объекты операционной системы требуется явно освобождать, как и память, выделенную динамически. В другие языки добавили механизмы, которые позволяют про это почти не думать – блок finally и сборщик мусора. К подходу как правильно писать код, приходится себя приучивать.
Пример:
В унаследованном из языка С коде часто приходится отказываться от использования исключений, поскольку код не будет правильно с ними работать и его придется целиком переписать, чтобы все было в порядке.
Еще одна проблема:
Foo(new Test(), new Test2())
Такой код развернется во что-то такое:
Test* test = new Test();
Test2* test2 = new Test2();
Foo(test, test2)
Если new Test2() бросит исключение, то будут утечки: Test никто никогда не очистит...
Исключения в конструкторах. Если исключение произошло в конструкторе, то деструктор для объекта не вызовется, поскольку объект не создан до конца (только для базовых классов вызовется).
Пример:
#include "stdafx.h"
#include <stdio.h>
#include <exception>
#include <iostream>
#include "Person.h"
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "Base destructor called\n";
}
};
class Epicness : public Base
{
public:
Epicness(int argument)
{
if (argument < 1)
throw std::exception("Argument is invalid.");
}
~Epicness()
{
cout << "Epicness destructor called\n";
}
};
int main()
{
Epicness* epic = NULL;
try
{
epic = new Epicness(-1);
}
catch (const std::exception& e)
{
delete epic;
}
delete epic;
system("pause");
return 0;
}
Вывод:
Base destructor called
2.4 Пример 2. Исключение и память, проброс
Когда возникает исключение, за ним может остаться след из неподчищенных ресурсов. С одной стороны при закрытии приложения все связанные с ним ресурсы почистятся автоматически, а с другой стороны, поскольку вызывающий код может, к примеру, захотеть попробовать снова соединиться с базой данных, нужно сделать, чтобы не было утечек памяти. Как с ними бороться?
Пример:
#include "stdafx.h"
#include <stdio.h>
#include <exception>
#include <iostream>
#include "Person.h"
using namespace std;
void DoSomething()
{
throw std::exception("File is missing."); // некоторая ошибка в бизнес-логике, например файл не найден. Бросаем исключение.
}
void ProcessFile()
{
char* fileContent = new char[100];
try
{
DoSomething();
// бизнес-логика, которая может бросать исключения.
}
catch (...) // мы не можем обработать исключение,
// но нам важно почистить память, выделенную динамически.
{
delete[] fileContent; // чистим ресурсы, за которые отвечаем
throw; // пробрасываем брошенное исключение дальше
}
delete[] fileContent;
}
int main()
{
try
{
ProcessFile();
}
catch (const std::exception& e)
{
cerr << e.what() << endl;
}
system("pause");
return 0;
}
Как видите, код, который выделял динамическую память, отвечает и за ее очистку - в нем находится и код по ее очистке. Очистка произойдет и в случае исключений. После того, как код сделал очистку, он возобновляет исключение. Сама обработка исключительной ситуации происходит в main, на самом высоком уровне.
Более продвинутый подход – использовать умные указатели. О них речь пойдет в следующих лекциях.
2.5 Исключения в деструкторах
Нельзя бросать исключения в деструкторах.
3 Приближение
3.1 Спецификация исключений
В язык добавили возможность добавлять для методов и подпрограмм информацию, какие исключения бросаются. Идею, насколько я понимаю, взяли из Java. Идея не выстрелила. От нее отказались.
3.2 Обработка неожидаемых исключений
По-умолчанию, когда происходит исключение, приложение закрывается. Однако этот механизм можно переопределить, например, не закрывать приложение, а продолжить его выполнение. Идея в С++ не выстрелила, поскольку ее поведение сильно зависит от компилятора. Однако в других языках программирования она применяется.
Идея за применением такая: когда произошла исключительная ситуация, которую никто не обработал, значит нужно подготовить информацию для отправки программисту: дамп памяти, лог, показать красивое окошко с извинениями кнопкой отправить сведения об ошибке разработчикам.
