---
# System prepended metadata

title: Exceptions
tags: [cpp, ' exceptions', ' teaching', ' mipt', ' 3sem']

---

---
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);
}
```