---
title: Exceptions
tags: cpp, mipt, 3sem, teaching, exceptions
---
# Восьмое занятие
#### 2022-10-22
## Исключения
### Основная информация
Способ контроля работы программы.
Пример: выделение динамимческой памяти. Оператор `new` может вызвать (или же *выбросить*) исключение [`std::bad_alloc`](https://en.cppreference.com/w/cpp/memory/new/bad_alloc). Исключение свидетельствует о том, что не удалось выделить достаточное количество памяти. В таком случае возникает желание уменьшить количество выделяемой памяти. Делается это следующим образом:
```cpp=
#include <iostream>
int main() {
double* ptr;
try {
ptr = new double[20000000];
} catch (std::bad_alloc const& err) {
std::cout << err.what() << '\n';
ptr = new double[20000];
} catch (...) {
std::cout << "Something very bad!\n" << std::endl;
}
delete [] ptr;
return 0;
}
```
В данном примере мы увидели использование `try-catch`-блока. В `try` оборачивается блок, который потенциально может вызвать ошибку. В следующем блоке попытка обработать данную ошибку. И последний блок ловит ошибку в любом случае, что означает очень печальное состояние программы.
Еще один пример: деление на ноль.
```cpp=
#include <iostream>
#include <string>
double inv(double x) {
if (x == 0) {
throw std::string("Exception!");
}
return 1. / x;
}
int main() {
inv(0.0);
return 0;
}
```
В момент попадения на строку 6 начинается уничтожение всех локальных переменных, а потом постепенное разворачивание стека вызова до момента попадения в `main`. После этого программа сообщает операционной система о результате работы и все завершается аварийно (аварийное завершение).
Как видно по сигнатуре `throw` возможен выброс **любого** объекта. Соответственно, в блоке `catch` можно ловить объекты любого типа (почти). В блоке `catch` можно ловить объект с копированием, по ссылке и даже по указателю.
`dynamic_cast<T>` -- еще один из стандартных операторов, который кидает исключение.
### Что НЕ есть исключение
Исключением являеется все то, что описано в стандарте как исключение
- Обращение к невыделенной памяти -- не исключение (это undefined behavior)
- Выход за границы массива -- не исключение (это undefined behavior)
- Неправильное разыменование указателя -- не исключение
Исключение можно обработать, непредвиденные ситуации решению не подлежат (пока).
### Обработка ошибок
Проведем проверку работы `throw`.
```cpp=
#include <iostream>
struct S() {
S() { std::cout << "S()\n"; }
S(S const&) { std::cout << "S(S const&)\n"; }
S& operator=(S const&) { std::cout << "operator=(S const&)\n"; }
~S() { std::cout << "~S()\n"; }
}
void f() {
S s;
throw s;
}
int main() {
try {
f();
} catch(S exc) {
std::cout << "cought S in main\n";
}
return 0;
}
```
Получается, что при выкидывании ошибки происходит копирование объекта. Если ошибка ловится по значению, то она снова копируется.
В блоке `catch` можно провести очередной *бросок* ошибки. И снова повторится процедура копирования. Это можно избежать, если ловить по константной ссылке.
### Приведение типов
При выбрасывани объектов они ловятся строго в соотвествии с их типом. Не происходит неявного преобразования. Но есть исключение в данном случае.
Если выбрасывается объект `Derived`, который является наследником `Base`, то можно словить объект по ссылке на базовый класс.
Стоит отметить, что не стоит ловить производные классы после базовых. Это просто не сработает.
### Исключение в конструкторах
```cpp=
struct MyArray {
size_t size = 0;
int* buffer;
int* buffer_add;
MyArray() {
size = 5;
buffer = new int[5];
throw std::string("Error");
buffer_add = new int[5];
}
~MyVector() {
delete [] buffer;
delete [] buffer_add;
}
}
```
### Исключение в деструкторах
Исключение в деструкторах запрещены. Но если сильно захотеть, то можно
### noexcept
Чтобы указать на неисключительность вашего метода (убедить компилятор, что вы не будете выкидывать исключение из данного метода), существует модификатор `noecxept`.
```cpp=
#include <iostream>
#include <vector>
class MyClass {
public:
MyClass(int n, float f): size(n), ptr(new float[size]) {
for (int i = 0; i < n; ++i) {
ptr[i] = f;
}
std::cout << "MyClass()\n";
}
MyClass(const MyClass& other): size(other.size), ptr(new float[size]) {
for (int i = 0; i < size; ++i) {
ptr[i] = other.ptr[i];
}
std::cout << "MyClass(const MyClass& other)\n";
}
MyClass(MyClass&& other) {
if (this == &other) {
return;
}
size = other.size;
ptr = other.ptr;
other.size = 0;
other.ptr = nullptr;
std::cout << "MyClass(const MyClass&& other)\n";
}
~MyClass() {
std::cout << "~MyClass()\n";
}
private:
int size;
float* ptr;
};
int main() {
std::vector<MyClass> vec;
std::cout << "Push Back\n";
vec.push_back(MyClass(12, 3.1415f));
std::cout << "Emplace Back\n";
vec.emplace_back(1, 2.71828f);
std::cout << "final point of program\n";
return 0;
}
```
## Задание
1. Реализуйте свое исключение. Вам потребуется базовый класс для исключения `base_exception` и отнаследуйтесь от него для создания двух видов ошибок: `math_exception` и `invalid_argument`. Первое исключение вызывается при делении на ноль, второе -- при получении отрицательного значения при подсчете корня квадратного. Дожен быть реализован метод `what()`. Провести проверку работы данных исключений
```cpp=
#include <iostream>
#include <cmath>
struct base_exception {
// smth here
};
struct math_exception final: base_exception {
// smth here
};
struct invalid_argument final: base_exception {
// smth here
};
double inverse(double x) {
if (x == 0)
throw math_exception();
return 1. / x;
}
double root(double x) {
if (x < 0)
throw invalid_argument();
return std::sqrt(x);
}
```
2. Реализуйте вектор по следующему шаблону:
```cpp=
template <typename T>
class Vector {
public:
Vector();
Vector(Vector const& vec);
Vector(Vector&& vec) noexcept;
void push_back(const T& value);
T& top(); // Безопасное получение ссылки на верхний элемент;
// Если его нет, то исключение
void pop(); // Безопасное извлечение (удаление) верхнего элемента;
// Если его нет, то исключение
void is_empty() const;
void capacity() const;
T& at(size_t index); // Безопасное получение ссылки на i-ый элемент;
// Если его нет, то исключение
T& operator[](size_t index); // Получение ссылки на i-ый элемент
const T& operator[](size_t index) const; // Получение const ссылки на i-ый элемент
private:
size_t size = 0;
T* buffer = nullptr;
void add_memory();
~Vector();
}
```
3. Попробовать разделить вектор на два класса:
- `Buffer` -- предоставляет механизм работы с памятью
- `Vector` -- интерфейс, который является оберткой над `Buffer`. Самостоятельно лишь описывает правила работы с `Buffer`, но не лезет в память напрямую.
```cpp=
template <typename T>
class Buffer {
private:
T* buffer;
public:
Buffer();
T& top();
private:
void add_memory();
T& operator[](size_t index;)
const T& operator[](size_t index);
}
```