C++ lectures at MIPT (in Russian). Lecture 4. Exceptions, part 2

preview_player
Показать описание
Лекции в магистратуре МФТИ по C++ на русском языке.

Вторая часть лекции по исключениям посвящена вопросам проектирования с учётом наличия исключений

Лектор: Константин Владимиров
Дата лекции: 8 октября 2019 года
Съёмка: Дмитрий Рябцев

Список замеченных ошибок в лекции:
* 50:33 на самом деле вылет исключения из noexcept функции это вызов std::terminate
Рекомендации по теме
Комментарии
Автор

С noexcept у конструкторов (в т.ч. копирующих и перемещающих) пользовательских классов есть такая засада — многие вещи в стандартной библиотеке работают по разному когда у конструкторов есть noexcept и когда его нет.
Разумеется, при noexcept появляется возможность оптимизаций.
Засада в том, что например писать const у константных методов нас сначала приучает компилятор, а уже потом это входит в хорошую привычку, а вот писать всегда noexcept, constexpr и прочие "новые" ключевые слова у многих привычки нет.
И компилятор тоже молчит, а то что перемещающий конструктор работает как копирующий из-за того что где то там мы забыли noexcept выясняется уже при профилировании.

ufabiz
Автор

1:15:11 обычная ситуация в Java, иногда весело читать стектрейсы на сотни строк, но зачастую это реально полезно, когда нужно пробрасывать исключения по слоям абстракций и разным фреймворкам. Реализация TCP/IP стека вернула ошибку связи, драйвер СУБД сделал из нее ошибку подключения, фреймворк сделал из нее ошибку абстрактного слоя работы с данными, бизнес логика сделала ошибку "ну не шмогла я перевести денег на счет", GUI сделал из этого какой-то эксепшн, который отобразил окошко с ошибкой на экране, а весь гигантский стектрейс залогировал. Этого оказалось мало и в Java 7 ещё стало можно связывать братьев - чтобы одно исключение не скрывало другое. Если вернуться к C++, то в 23 стандарте хотят стектрейсы сделать доступными разработчикам, кейсов для такой фишки добавится.

samolisov
Автор

С <system_error> какая-то накладка в этой лекции: путаете error_category и error_condition - всё в одну кучу. В остальном лекции замечательные

kyookuhmbuh
Автор

Монадой Maybe, скорее, является std::optional (хотя технически монадой при этом и не является, но именно он нацелен на эту позицию в будущих стандартах), std::expected ближе к Either и, скорее, является его частным примером

Почему std::expected не попал в стандарт, а std::optional не является монадой? Прежде, чем появиться монадическим операциям, в комитете хотят решить сложную дизайнерскую проблему, чтобы не переделывать ещё раз в будущем, см.

Что же касается общего подхода к обработке исключительных ситуаций, мне нравится другое предложение — operator try, open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0779r0.pdf, использующий механизм, похожий на корутины. Он позволяет внутри реализации оператора принять решение — развернуть std::optional/std::expected, достав из него значение, либо же выйти из функции. Условно, так:

std::optional<int> get_int();

std::optional<std::string> get_string() {
int x = try get_int();
return std::to_string(x);
}

Если в operator try() из get_int() придёт nullopt, он просто выйдет из get_string() с nullopt. Если придёт optional, содержащий значение, operator try() вытащит его и вернёт значение переменной x, не прерывая выполнение get_string(). Это не универсальный способ обработки ошибок, но там, где он применим — он один из самых приятных. Нельзя проигнорировать возврат — не сойдутся типы, это ошибка компиляции. Но если программист достаточно уверен в себе, он может принять std::optional<int> и полностью взять на себя ответственность за проверку валидности содержимого optional

Не знаю, можно ли его назвать локальным способом обработки исключений, но к локальным он определённо ближе, чем к нелокальным

oficsu
Автор

27:20 "За счет того что первым уничтожается базовый класс". Мне кажется что это справедливо только в данном контексте (в дочернем нечего уничтожать - он не создан еще). Первым уничтожается не "базовый класс", а последний созданный - хоть, в данном случае, это ближайший базовый. Контекст не вполне заметен тем кто с этим еще не знаком. В данном случае конструктор MyVectorBuf - это единственный из отработываших на данный момент. Поэтому считается созданным и для него идет отработка деструктора, а для MyVector деструктор не отрабатывает - т.к. и конструктор его еще не отработал - возникло исключение. Было бы совсем неправдой если б иерархия была чуть длинее. Или я как-то не так понял.

namydna
Автор

На 25:25 логически правильнее передавать в конструктор базового класса rhs.size_ вместо rhs.used_ (стр.64 файл 04-better-myvec-98.cc)

rodionmontsarj
Автор

Кажется, в 50:33 закралась небольшая ошибка. В Стандарте, раздел except.spec, пункт 9 (по состоянию на C++14 n3690, в драфте 20ых n4861 тоже есть, это будет пункт 6), написано: Whenever an exception is thrown and the search for a handler (15.3) encounters the outermost block of afunction with anexception-specificationthat does not allow the exception, then, — if the exception-specification is a dynamic-exception-specification, the function std::unexpected()is called (15.5.2), — otherwise, the function std::terminate()is called (15.5.1).

Кидать исключения из noexcept функций вполне законно. Это надо понимать как "клянусь жизнью, исключений не будет. пристрелите, если нарушу обещание".

allcreater_
Автор

template<typename FwdIter>
void destroy(FwdItrer first, FwdIter last)
{
while(first++ != last) <- Первый элемент пропускается. Наверное стоит делать ++ в след. строчке
destroy(&*first);

}

MrSnarpix
Автор

1:20:20 проверку из cppreference на тему находимся ли в процессе размотки стека можно сломать если время жизни объекта не так жестко привязано к локальным скоупам, как у локальных переменных. Причем как кажется можно построить как искусственный пример, в котором объект будет считать что уничтожается в процессе раскрутки стека, так и наоборот будет считать что уничтожается в обычных условиях, когда на самом деле происходит раскрутка стека.
Для демонстрации можно представить что объект создается в динамической памяти и на него запоминается указатель, который освобождается в другом скоупе.
Или например в паттерне команда (или аналогичных ситуациях), тоже кажется может нарушаться.
Если мы создаем проверяемый объект, в деструкторе которого важно знать, находимся ли в процессе размотки стека, и передаем его во владение другому объекту. В этом случае если этот объект удаляется одновременно с владельцем, то имеет смысл передавать начальный счетчик от владельца, а не использовать значение из конструктора (а владельцу счетчик сообщать от его владельца и т.д). Но в общем случае, если у проверяемого объекта несколько владельцев через shared_ptr, либо если объект может быть уничтожен пока владелец еще жив, то мне кажется что гарантировать что-то сложно.

struct LogEntry {
const size_t count = std::uncaught_exceptions();
~LogEntry() { std::cout << count << "-" << std::uncaught_exceptions() << std::endl; }
};
struct A {
std::vector<LogEntry>& logs;
A(std::vector<LogEntry>& logs) : logs(logs) {}
~A() { logs.push_back(LogEntry()); }
};

std::vector<LogEntry> logs;
try {
auto a = A(logs);
throw 1;
}
catch (int) {
}

ivankorotkov
Автор

48:08
> throw() скорее всего выпилят из языка
Он уже был частично убран в С++17 (там где throw был с аргументами), а в С++20 его окончательно убрали

misana
Автор

35:06 строки кода 97 и 98, кажется вместо tmp.size() нужно tmp.used()? size строкой выше будет больше used

andrewnec.
Автор

В районе 25ой минуты MyVector наследует MyVectorBuf, но у последнего деструктор - невиртуальный.

valentynvovk
Автор

Переводить unwinding как "размотка" - это плохая идея. Потому что стек после unwinding становится меньше, съеживается. Слово же "размотка" (раскрутка, развертка) в русском языке подразумевает, что объект становится больше, длиннее. Что в случае со стеком в корне неверно.

Есть предложение - говорить "свертка" стека. Это более соответствует действительности, т.к. стек действительно сворачивается. Ну и свертка это ближе к математической области, а не к ткацкой :)

vasilystarostin
Автор

А что за магистра тура Intel ? Как правильно называется специальность?

konstantinmorozov