вторник, 20 июля 2021 г.

[prog.thoughts] Мое текущее отношение к поддержке исключений в C++

В этот блог-пост вошли соображения, которые не хотелось впихивать в предыдущий пост дабы не раздувать его размер совсем уж до безобразия.

Попробую рассказать о том, как я сам сейчас отношусь к существующей в C++ поддержке исключений.

Ключевое

Хорошо, что исключения в C++ есть. Как уже говорилось в предыдущем посте, лично я плохо себе представляю как писать надежный код без использования исключений при программировании в стиле "современного C++".

Плохо то, что текущая реализация исключений:

  • во-первых, ставит разработчика перед выбором об использовании или неиспользовании исключений в принципе;
  • во-вторых, даже при использовании исключений не всегда легко понять, стоит ли в каком-то конкретном месте применять исключения или же нет. Просто из соображений скорости работы кода.

А все потому, что сейчас исключения слишком дорогие (тяжелые). Это касается и стоимости бросания исключений, и стоимости хранения в исполняемом файле информации для поиска обработчиков исключений (что-то вроде урезанной версии RTTI). Плюс к тому, исключения не предсказуемы в том плане, что заранее не просчитаешь, сколько же займет выброс и обработка исключения, что не позволяет применять исключения в системах жесткого реального времени.

Поэтому главная претензия к существующим в C++ исключениям -- это их высокая стоимость из-за чего в ряде случаев приходится от использования исключений отказываться. А это ведет к зоопарку способов информирования об ошибках и к фрагментации C++ной экосистемы.

Что еще мне не нравится в существующем C++

Ниже попробую коротко пройтись по другим моментам, которые мне не нравятся в текущем C++. Не думаю, однако, что эти пункты когда-либо будут исправлены. Но это же не повод не написать о том, что гложет, не так ли? ;)

Возможность выбросить всё, что угодно

Если я правильно понимаю, когда в C++ добавили возможность работы с исключениями, стандартных базовых классов для исключений (std::exception, std::runtime_error и т.д.) еще не было. Поэтому в C++ можно бросить не только класс-наследник std::exception, но и простой int или const char *.

Лично я думаю, что это дурацкое наследие темного прошлого, от которого нужно избавиться раз и навсегда.

Сам я придерживаюсь правила о том, что все бросаемые в коде исключения должны быть наследниками std::exception. И, по большому счету, пишу код, который может не пережить, если кто-то бросит простой int. Не то, чтобы я много писал чего-то вроде:

try
{
  ...
}
catch(const std::exception &) {
  ... // Отмена каких-то операций.
  throw// Проброс исходного исключения дальше.
}

Но иногда приходится. И не всегда я вспоминаю о том, что желательно было бы добавить еще и catch(...), а это может выйти боком когда-нибудь.

Иерархии исключений

Очень неоднозначная штука, которая изначально мне очень нравилась, но потом перестала.

С одной стороны, удобно, когда можно завести базовый класс для всех исключений библиотеки X. Тогда элементарно просто отлавливать все исключения, которые могут вылетать из X.

С другой стороны, мне сложно припомнить когда на практике была бы польза от развесистых иерархий классов исключений. Обычно хватает какого-то одного класса, куда описание возникшей проблемы складывается в виде строки.

При этом, насколько я понимаю, именно наличие таких иерархий является одной из причин высокой стоимости поддержки исключений: в программе нужно иметь какой-то вариант RTTI для того, чтобы разбираться с наследованием, а при пробросе исключения нужно посредством этого варианта RTTI искать подходящий обработчик для исключения.

Так что штука хорошая, но дорогая. И, может быть, если бы в C++ ограничились более простой версией (возможность объявить собственный тип исключения, но без возможности наследоваться от него), то это бы привело к более дешевой поддержке исключений. А это бы сузило круг задач, в которых исключения находятся под запретом.

Недоделанный noexcept

Повторяться не буду, т.к. писал уже неоднократно, в том числе и в прошлом посте. Но тезисно обозначу, что кроме спецификатора noexcept и выражения noexcept, в C++ мне очень не хватает noexcept-блока. Чтобы компилятор бил меня по рукам, если написал не-noexcept код внутри noexcept-блока.

Пару слов о предложении добавить в C++ т.н. deterministic exceptions

Несколько лет назад Герб Саттер написал предложение по добавлению в C++ т.н. deterministic exceptions: Zero-overhead deterministic exceptions: Throwing values (ссылка дана на последнюю на момент написания этих строк ревизию).

Лично я скептически отношусь к этому предложению.

Да, там правильно говорится о том, что существующие динамические исключения не могут использоваться в определенных областях и это плохо. Исключения все-таки нужны, но a) гораздо более легковесные и b) с предсказуемым временем обработки.

Но вот альтернатива в виде предлагаемых deterministics exceptions не решает проблему существующих исключений. А вводит в язык еще один механизм, у которого есть свои достоинства и недостатки.

В итоге может получиться как в известном комиксе: у нас есть 14 конкурирующих стандартов и с этим нужно что-то сделать... Хоба! И у нас уже 15 конкурирующих стандартов.

Раньше у нас были возвращаемые значения (разных видов) и динамические исключения. Плюс недоделанный noexcept. А если Саттеровское предложение примут, то к этому зоопарку добавятся еще и deterministic exceptions. После чего разработчиков оставят заниматься любовью со всем тем бардаком, который уже накопился в C++ мире.

Так что, имхо, нужно фиксить существующий механизм исключений (возможно даже путем слома совместимости между стандартами), а не добавлять еще один способ выстрелить себе в ногу.

Кстати говоря, в свое время я уже фиксировал краткие впечатления от предложений Саттера, желающие могут найти их здесь. С тех пор ничего принципиально не поменялось. И да, за предложение валить все приложение вместо выбрасывания bad_alloc нужно отрывать руки. Не глядя на авторитеты и заслуги.

Тут новое предложение в комитет по стандартизации подвезли: P2232

Только недавно узнал, совершенно случайно.

P2232: Zero-Overhead Deterministic Exceptions: Catching Values.

Если оттуда исключить дурацкие (сорри за мое оценочное суждение) идеи по бросанию и поимке сразу нескольких экземпляров исключений, то может получиться интереснее, чем в предложениях Саттера. Почти как то, что я сам бы хотел видеть.

Что хотелось бы видеть в C++ (но вряд ли это был бы C++)

Не буду здесь еще раз повторять то, что однажды уже было сформулировано с примерами и пояснениями. Поэтому дам ссылку на блог-пост годичной давности: "Какой способ информирования об ошибках мне бы хотелось иметь для написания надежного кода?"

То, что там было написано год назад все еще является моим актуальным мнением.

Комментариев нет: