Слайд 2Обработка ошибок и непредвиденных ситуаций
В процессе работы программы возможно возникновение
непредвиденных ситуаций
Нехватка памяти и других системных ресурсов
Ошибки ввода-вывода
Некорректные данные, поступившие
от пользователя
Нарушение целостности данных (поврежден файл с данными)
Некорректные параметры функций
Хорошо спроектированная программа должна уметь обнаруживать, сигнализировать и обрабатывать данные ситуации
Сообщить об ошибке пользователю
Попытаться исправить ошибки
При невозможности дальнейшей работы – сохранить данные и завершить работу
Слайд 3Обнаружение ошибочных ситуаций
Проверяйте данные поступающие из внешних источников на корректность
Файлы,
введенные пользователем данные, сетевые пакеты и т.п.
Осуществляйте проверку успешности вызовов
функций используемого API
Функции ввода/вывода, выделения/освобождения ресурсов
Слайд 4Способы обработки ошибок
Проигнорировать ошибку, оставив программу в неопределенном состоянии
Самых худший
вариант – никогда так не делайте!
Вывод сообщения об ошибке и
аварийное завершение работы программы
Немногим лучше предыдущего
Поместить код ошибки в глобальную переменную
Проблемы с многопоточными приложениями
Предусмотреть специальное значение функции, сигнализирующее об ошибке
А как возвращать нормальное значение?
Вызвать функцию-обработчик ошибки
Часто необходимо передавать контекст возникновения ошибки
Необходимо восстанавливать нормальное выполнение
Воспользоваться механизмом исключений языка C++
Слайд 5Механизм исключений
Встроенное в язык C++ средство для обработки внештатных ситуаций
во время выполнения программы
Исключения позволяют программе обработать внештатную ситуацию на
более высоком уровне, на котором возможно восстановить нормальную работу программы
Обработка исключений осуществляется кодом вне обычного потока выполнения
Слайд 6Оператор try-catch
Синтаксис
try
{
код, в котором возможно генерирование исключений
}
catch(объявление исключения)
{
код, обрабатывающий исключение
}
[catch
(…)
{
код, обрабатывающий исключения любого типа
}]
Слайд 7Оператор throw
Синтаксис:
throw [выражение]
Выражение может быть любого типа (кроме void)
При выполнении
данного оператора
создается объект исключительной ситуации на основе объекта, выступающего в
качестве аргумента оператора throw
Дальнейшее выполнение программы прерывается, происходит поиск подходящего обработчика в защищенном блоках try-catch
Если подходящий обработчик найден, то происходит раскрутка стека (stack unwinding), при которой разрушаются все автоматические объекты, созданные между началом соответствующего блока try
Если обработчик не найден, происходит завершение работы программы
Слайд 8Классы исключений
Использование классов в качестве типа объектов исключений имеет ряд
преимуществ
Возможность хранения подробной информации об исключении
Возможность использования полиморфизма
Оператор catch ловит
исключения не только указанного типа, но и всех производных от него типов:
try
{
throw CDerivedException;
}
catch (CBaseException const& e)
{
}
Слайд 9Выбрасывание и перехват исключений в C++
Исключения всегда выбрасываются «по значению»
У
классов исключений должен быть доступен конструктор копирования
Перехват исключений должен происходить
по ссылке
В противном случае может произойти «урезание» информации об исключении
Слайд 10#include
#include
#include
#include
double MySqrt(double arg)
{
if (arg < 0)
{
throw
std::invalid_argument("the argument must be >= 0");
}
return sqrt(arg);
}
int main()
{
try
{
std::cout
= " << MySqrt(3) << "\n";
std::cout << "sqrt(-1) = " << MySqrt(-1) << "\n";
}
catch (std::invalid_argument const & e)
{
std::cout << "Error: " << e.what() << "\n";
}
return 0;
}
Output:
sqrt(3) = 1.732051
Error: argument must be >= 0
Слайд 11Перевыброс исключения
Перехватив исключение в блоке catch можно перевыбросить его снова,
чтобы оно было перехвачено внешним обработчиком
Синтаксис:
throw;
Пример:
try
{
...
}
catch(SomeError const& e)
{
//...
throw;
}
Слайд 12Стандартные классы исключений библиотеки STL
Слайд 13Основные классы исключений STL
exception – базовый класс для всех исключений,
выбрасываемых кодом библиотеки
logic_error – базовый класс для ошибок, которые можно
было бы выявить до выполнения программы
runtime_error – базовый класс ошибок, которые, как правило, можно выявить только во время работы программы
bad_alloc – ошибка выделения памяти
bad_cast – ошибка приведения типа
Слайд 14Преимущества использования исключений
Реакция на исключение происходит всегда
Коды ошибок работают только
тогда, когда их проверяют
Возможность различной реакции на различные типы исключений
Объекты,
выбрасываемые при исключении могут нести доп. информацию об исключительной ситуации
имя файла и номер строки, сообщение об ошибке, код системной ошибки и т.п.
Слайд 15Проблемы
При использовании исключений необходимо иметь представление о том, какие исключения
могут быть выброшены в результате работы функции или метода класса
Увеличение
размеров машинного кода и некоторое снижение его быстродействия
Необходимость разработки кода, устойчивого к возникновению исключений
Слайд 16Выброс исключения в конструкторе
При выбросе исключения в теле конструктора или
в списке инициализации процесс конструирования экземпляра класса прерывается и он
считается не созданным
Деструктор для такого класса вызван не будет
Будут вызваны деструкторы для тех полей класса, для которых успели выполниться конструкторы
Порядок вызова конструкторов полей класса совпадает с порядком их перечисления в объявлении класса
Порядок вызова деструкторов – обратный порядку вызова конструкторов
Слайд 17class A
{
public:
A(std::string const& name)
:m_name(name)
{
std::cout
size_t size)
:m_a(name)
,m_pData(new int[size])
,m_size(size)
{
std::cout << "B::B(" << m_size << ")\n";
}
~B()
{
delete [] m_pData;
std::cout << "B::~B(" << m_size << ")\n";
}
private:
A m_a;
size_t m_size;
int * m_pData;
};
int main()
{
try
{
B b2("Test", 1000000000);
}
catch (std::bad_alloc const & e)
{
std::cout << "Error: " << e.what() << "\n";
}
return 0;
}
Output:
A::A(Test)
A::~A(Test)
Error: bad allocation
Нехватка памяти и выброс исключения std::bad_alloc
Слайд 18Выброс исключений в деструкторе
Не допускайте выброса исключений в деструкторах объектов
В
C++ выброс исключения в деструкторе приводит к аварийному завершению работы
программы
Слайд 19Exception-safe programming
Разработка кода, безопасного к возникновению исключений
Слайд 20Код, устойчивый к возникновению исключений
Объект, как минимум, должен оставаться destructible
– последующий вызов деструктора данного объекта не должен приводить к
сбою в приложении или неопределенному поведению
При выбросе исключений не должно происходить утечек памяти и других ресурсов
Объект, желательно, должен сохранить свою целостность, т.е, последующие вызовы методов объекта не должны приводить к сбоям или неопределенному поведению
Желательно, объект должен вернуться в предсказуемое состояние, а еще лучше, в состояние, в котором он был до вызова метода, выбросившего исключение
Слайд 21class CMyString
{
public:
CMyString(const char * str):m_size(strlen(str))
{
m_pChars = new char[m_size + 1];
memcpy(m_pChars,
str, m_size);
m_pChars[m_size] = '\0';
}
CMyString(CMyString const& other)
:m_size(other.m_size)
,m_pChars(new char[other.m_size + 1])
{
memcpy(m_pChars,
other.m_pChars, m_size + 1);
}
~CMyString()
{
delete [] m_pChars;
}
CMyString& operator=(CMyString const& other)
{
if (this != &other)
{
delete [] m_pChars;
m_size = other.m_size;
m_pChars = new char[other.m_size + 1];
memcpy(m_pChars, other.m_pChars, m_size + 1);
}
return *this;
}
private:
char * m_pChars;
size_t m_size;
};
Код конструктора безопасен к возникновению исключений
Код конструктора копирования безопасен к возникновению исключений
Выброс исключения в операторе new нарушит целостность объекта и возможность его корректного разрушения: в деструкторе произойдет повторное удаление массива m_pChars.
Слайд 22class CMyString
{
public:
CMyString(const char * str):m_size(strlen(str))
{
m_pChars = new char[m_size + 1];
memcpy(m_pChars,
str, m_size);
m_pChars[m_size] = '\0';
}
CMyString(CMyString const& other)
:m_size(other.m_size)
,m_pChars(new char[other.m_size + 1])
{
memcpy(m_pChars,
other.m_pChars, m_size + 1);
}
~CMyString()
{
delete [] m_pChars;
}
CMyString& operator=(CMyString const& other)
{
if (this != &other)
{
delete [] m_pChars; m_pChars = NULL;
m_size = other.m_size;
m_pChars = new char[other.m_size + 1];
memcpy(m_pChars, other.m_pChars, m_size + 1);
}
return *this;
}
private:
char * m_pChars;
size_t m_size;
};
Код конструктора безопасен к возникновению исключений
Код конструктора копирования безопасен к возникновению исключений
При выбросе исключения объект станет destructibe, но целостность объекта будет нарушена для его последующего использования:
переменная m_size будет содержать некорректную длину, а m_pChars будет равен NULL
Слайд 23class CMyString
{
public:
CMyString(const char * str):m_size(strlen(str))
{
m_pChars = new char[m_size + 1];
memcpy(m_pChars,
str, m_size);
m_pChars[m_size] = '\0';
}
CMyString(CMyString const& other)
:m_size(other.m_size)
,m_pChars(new char[other.m_size + 1])
{
memcpy(m_pChars,
other.m_pChars, m_size + 1);
}
~CMyString()
{
delete [] m_pChars;
}
CMyString& operator=(CMyString const& other)
{
if (this != &other)
{
char * pNewChars = new char[other.m_size + 1];
m_size = other.m_size;
delete [] m_pChars; m_pChars = pNewChars;
memcpy(m_pChars, other.m_pChars, m_size + 1);
}
return *this;
}
private:
char * m_pChars;
size_t m_size;
};
Код конструктора безопасен к возникновению исключений
Код конструктора копирования безопасен к возникновению исключений
При выбросе исключения не происходит утечек памяти, а объект остается в том же состоянии, в каком он был до вызова оператора =
Безопасность оператора присваивания достигнута
Слайд 24class CMyString
{
public:
CMyString(const char * str):m_size(strlen(str))
{
m_pChars = new char[m_size + 1];
memcpy(m_pChars,
str, m_size);
m_pChars[m_size] = '\0';
}
CMyString(CMyString const& other)
:m_size(other.m_size)
,m_pChars(new char[other.m_size + 1])
{
memcpy(m_pChars,
other.m_pChars, m_size + 1);
}
~CMyString()
{
delete [] m_pChars;
}
CMyString& operator=(CMyString const& other)
{
if (this != &other)
{
CMyString copy(other);
std::swap(m_pChars, copy.m_pChars);
std::swap(m_size, copy.m_size);
}
return *this;
}
private:
char * m_pChars;
size_t m_size;
};
Код конструктора безопасен к возникновению исключений
Код конструктора копирования безопасен к возникновению исключений
При выбросе исключения не происходит утечек памяти, а объект остается в том же состоянии, в каком он был до вызова оператора =, кроме того, код оператора = стал более понятным
Безопасность оператора присваивания достигнута
Слайд 25Правило разработки exception-safe кода
В каждом методе или функции выделите код,
который может выбрасывать исключения
Реорганизуйте код так, чтобы исключения не выбрасывались
в процессе изменения состояния программы (или объекта)
Лишь после того, как задача выполнена, можно изменять состояние программы используя операции, не выбрасывающие исключения
Слайд 26class CStringStack
{
struct Item
{
Item(std::string const& str, Item * p)
:data(str),pNext(p)
{
}
std::string data;
Item *
pNext;
};
public:
bool IsEmpty()const
{
return (m_pTop == NULL);
}
std::string Pop()
{
if (IsEmpty())
{
throw std::logic_error("Stack is empty");
}
std::string
result = m_pTop->data;
Item * pItem = m_pTop;
m_pTop = m_pTop->pNext;
delete pItem;
return result;
}
private:
Item * m_pTop;
};
Код конструктора безопасен к возникновению исключений
Создание копии строки может привести к выбросу исключения
При этом вытолкнутый элемент окажется безвозвратно потерян – результат возвращен не будет, а элемент из стека уже удален
Слайд 27class CStringStack
{
struct Item
{
Item(std::string const& str, Item * p)
:data(str),pNext(p)
{
}
std::string data;
Item *
pNext;
};
public:
bool IsEmpty()const
{
return (m_pTop == NULL);
}
void Pop(std::string & result)
{
if (IsEmpty())
{
throw std::logic_error("Stack
is empty");
}
result = m_pTop->data;
Item * pItem = m_pTop;
m_pTop = m_pTop->pNext;
delete pItem;
}
private:
Item * m_pTop;
};
Код конструктора безопасен к возникновению исключений
Такое решение решает проблему, однако фактически метод выполняет две задачи:
Выталкивает элемент с вершины стека
Возвращает вытолкнутое значение
При проектировании программных систем следует стремиться к тому, чтобы каждый модуль, класс и функция имели единственную четко заданную сферу ответственности
Слайд 28class CStringStack
{
struct Item
{
Item(std::string const& str, Item * p)
:data(str),pNext(p)
{
}
std::string data;
Item *
pNext;
};
public:
bool IsEmpty()const
{
return (m_pTop == NULL);
}
void Pop()
{
if (IsEmpty())
throw std::logic_error("Stack is empty");
Item
* pItem = m_pTop;
m_pTop = m_pTop->pNext;
delete pItem;
}
std::string GetTop()const
{
if (IsEmpty())
throw std::logic_error("Stack is empty");
return m_pTop->data;
}
private:
Item * m_pTop;
};
Код конструктора безопасен к возникновению исключений
Слайд 29Безопасность исключений и вопросы проектирования
Как правило, небезопасность к возникновению исключений
и плохое проектирование программ идут рука об руку
Часто небезопасный к
возникновению исключений код может быть достаточно легко исправлен
Однако, если невозможность такого исправления обусловлена архитектурой кода, скорее всего код плохо спроектирован
Слайд 30Гарантии безопасности исключений
Базовая гарантия безопасности
Скажи Нет! утечкам ресурсов
Безопасное разрушение и
использование объекта
Согласованное (не обязательно предсказуемое) состояние объекта
Строгая гарантия безопасности
Поддержка транзакционной
семантики выполнения методов «commit-or-rollback» - объект не меняет своего состояния при выбрасывании исключения
Гарантия отсутствия исключений
Функция или метод ни при каких условиях не выбрасывает исключений
Обязательное требование для деструкторов и ряда вспомогательных функций типа swap