mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
109 lines
5.5 KiB
Markdown
109 lines
5.5 KiB
Markdown
# Ложный `noexcept`
|
||
|
||
Начиная с 11 стандарта, мы можем помечать функции и методы спецификатором `noexcept`, говоря тем самым компилятору, что эта функция или метод не бросает исключения.
|
||
|
||
И вроде бы все хорошо: получив такую информацию, компилятор может не генерировать дополнительные инструкции для обработки раскрутки стека. Бинарники становятся меньше, а программы быстрее.
|
||
|
||
Но проблема в том, что этот спецификатор не заставляет компиляторы проверять,
|
||
что функция действительно не бросает исключений.
|
||
|
||
Если мы пометим функцию как `noexcept`, а она возьмет да кинет исключение,
|
||
произойдет что-то странное, заканчивающееся внезапным `std::terminate`.
|
||
|
||
Так, например, неожиданно [перестанут работать](https://godbolt.org/z/E9c9Ya) `try-catch` блоки.
|
||
|
||
```C++
|
||
void may_throw(){
|
||
throw std::runtime_error("wrong noexcept");
|
||
}
|
||
|
||
struct WrongNoexcept {
|
||
WrongNoexcept() noexcept {
|
||
may_throw();
|
||
}
|
||
};
|
||
|
||
// Попытки обернуть в try-catch эту функцию или любой код,
|
||
// использующий ее — бесполезны.
|
||
void throw_smth() {
|
||
if (rand() % 2 == 0) {
|
||
throw std::runtime_error("throw");
|
||
} else {
|
||
WrongNoexcept w;
|
||
}
|
||
}
|
||
```
|
||
|
||
Может быть очень сложно понять почему это произошло, если код разнесен по разным единицам трансляции.
|
||
|
||
## Условный `noexcept`
|
||
|
||
В С++ любят экономить на ключевых словах.
|
||
|
||
- `= 0` для объявления чисто виртуальных методов
|
||
- новый `requires` имеет два значения, порождая странные конструкции `requires(requires(...))`
|
||
- `auto` и для автовывода, и для переключения на trailing return type
|
||
- `decltype`, у которого разный смысл при применении к переменной и к выражению
|
||
- и, конечно, `noexcept` — точно также два значения как у `requires`.
|
||
|
||
Есть спецификатор `noexcept(condition)`. И просто `noexcept` — синтаксический сахар
|
||
для конструкции `noexcept(true)`.
|
||
|
||
А есть предикат `noexcept(expr)`, проверяющий, что выражение `expr` не кидает исключений по самой своей природе (сложение чисел, например) или же
|
||
помечено как `noexcept`.
|
||
|
||
И вместе они порождают конструкцию для условного навешивания noexcept:
|
||
```C++
|
||
void fun() noexcept(noexcept(used_expr))
|
||
```
|
||
|
||
```C++
|
||
void may_throw(){
|
||
throw std::runtime_error("wrong noexcept");
|
||
}
|
||
|
||
struct ConditionalNoexcept {
|
||
ConditionalNoexcept() noexcept(noexcept(may_throw())) {
|
||
may_throw();
|
||
}
|
||
};
|
||
|
||
// теперь с этой функцией все хорошо
|
||
void throw_smth() {
|
||
if (rand() % 2 == 0) {
|
||
throw std::runtime_error("throw");
|
||
} else {
|
||
ConditionalNoexcept w;
|
||
}
|
||
}
|
||
```
|
||
|
||
Чтобы избежать проблем, нужно всегда и везде использовать условный `noexcept` с аккуратной проверкой каждой используемой функции, либо вовсе не использовать `noexcept`. Но во втором случае стоит помнить,
|
||
что операции перемещения, а также `swap`, должны помечаться как `noexcept` (и быть действительно `noexcept`!) для эффективной работы со стандартными контейнерами.
|
||
|
||
Не забывайте писать негативные тесты. Без них
|
||
можно проморгать появление ложного `noexcept` и получить `std::terminate` на боевом стенде.
|
||
|
||
Также обратите внимание на тонкий и неприятный нюанс: если вам ну очень сильно надо кидать исключения из деструктора, обязательно явно пишите в его объявлении `noexcept(false)`. По умолчанию все ваши функции и методы помечены неявно `noexcept(false)`, но для деструкторов в C++ сделано исключение. Они неявно помечены `noexcept(true)`. [Так что](https://godbolt.org/z/5jo95d):
|
||
|
||
```C++
|
||
struct SoBad {
|
||
// invoke std::terminate
|
||
~SoBad() {
|
||
throw std::runtime_error("so bad dctor");
|
||
}
|
||
};
|
||
|
||
struct NotSoBad {
|
||
// OK
|
||
~NotSoBad() noexcept(false) {
|
||
throw std::runtime_error("not so bad dctor");
|
||
}
|
||
};
|
||
```
|
||
|
||
## Полезные ссылки
|
||
1. https://en.cppreference.com/w/cpp/language/noexcept
|
||
2. https://en.cppreference.com/w/cpp/language/noexcept_spec
|
||
3. https://www.modernescpp.com/index.php/c-core-guidelines-the-noexcept-specifier-and-operator
|