Подтвердить что ты не робот

Если вы находитесь в лагере "мы не используем исключения", то как вы используете стандартную библиотеку?

Примечание: Я не играю адвоката дьявола или что-то в этом роде. Мне просто очень любопытно, потому что я сам не в этом лагере.

Большинство типов в стандартной библиотеке имеют либо мутирующие функции, которые могут генерировать исключения (например, если происходит сбой памяти), либо не мутирующие функции, которые могут генерировать исключения (например, вне индексированных индексов). Кроме того, многие свободные функции могут генерировать исключения (например, operator new и dynamic_cast<T&>).

Как вы практически справляетесь с этим в контексте "мы не используем исключения"?

  • Вы пытаетесь никогда не вызывать функцию, которая может бросить? (Я не вижу, как это масштабируется, поэтому мне очень интересно услышать, как вы это делаете, если это так)

  • Вы в порядке со стандартной метализацией библиотеки, и вы относитесь к "мы не используем исключения" как "мы никогда не бросаем исключения из нашего кода, и мы никогда не ловим исключения из кода другого"?

  • Вы вообще отключите обработку исключений с помощью компиляторов? Если да, то как работают части, бросающие исключения из стандартной библиотеки?

  • РЕДАКТИРОВАТЬ. Ваши конструкторы могут ли они потерпеть неудачу или вы по договоренности используете двухэтапную конструкцию с выделенной функцией init, которая может возвращать код ошибки при сбое (который конструктор может 't), или вы делаете что-то еще?

РЕДАКТИРОВАТЬ Незначительное разъяснение через 1 неделю после начала вопроса... Большая часть содержания в комментариях и вопросах ниже фокусируется на том, почему аспекты исключений - "что-то еще". Я не заинтересован в этом, но когда вы решите сделать что-то еще, как вы справляетесь со стандартными библиотечными частями, которые генерируют исключения?

4b9b3361

Ответ 1

Я отвечу за себя и свой уголок мира. Я пишу С++ 14 (будет 17 раз, когда компиляторы получат лучшую поддержку), критически важные финансовые приложения с задержкой, которые обрабатывают гигантские суммы денег и никогда не могут опуститься. Набор правил:

  • исключений
  • no rtti
  • диспетчеризация без выполнения
  • (почти) отсутствие наследования

Память объединяется и предварительно распределяется, поэтому после инициализации нет вызовов malloc. Структуры данных либо бессмертны, либо тривиально-гибкие, поэтому деструкторы почти отсутствуют (есть некоторые исключения, такие как охранники). В принципе, мы делаем + + безопасность + шаблоны + lambdas. Конечно, исключения отключены с помощью коммутатора компилятора. Что касается STL, то хорошие его части (то есть: алгоритм, числовые, type_traits, iterator, atomic,...) могут использоваться. Части, бросающие исключения, совпадают с деталями выделения времени выполнения и частями полу-ОО, поэтому мы можем избавиться от всех трещин за один раз: потоки, контейнеры, кроме std:: array, std::string.

Зачем это?

  • Потому что, как OO, исключение предлагает иллюзорную чистоту, скрывая или перемещая проблему в другом месте, и затрудняет диагностику остальной программы. Когда вы компилируете без "-fno-exceptions", все ваши чистые и красиво выполняемые функции должны выдержать подозрение в том, что они не поддаются проверке. Гораздо проще иметь обширную проверку работоспособности по всему периметру вашей кодовой базы, чем сделать работу с efery невозможной.
  • Потому что исключения - это в основном GOTO дальнего действия, у которых есть неопределенное место назначения. Вы не будете использовать longjmp(), но исключения, возможно, намного хуже.
  • Потому что коды ошибок превосходят. Вы можете использовать [[nodiscard]] для принудительного вызова кода вызова.
  • Потому что иерархии исключений не нужны. В большинстве случаев нет смысла отличать то, что ошибочно, и когда это происходит, вероятно, из-за того, что разные ошибки требуют различной очистки, и было бы гораздо лучше явно сигнализировать.
  • Потому что у нас есть сложные инварианты для поддержания. Это означает, что есть код, однако глубоко в недрах, который должен иметь транснациональные гарантии. Есть два способа сделать это: либо вы делаете свои императивные процедуры настолько чистыми, насколько это возможно (то есть: убедитесь, что вы никогда не сработаете), или у вас есть неизменные структуры данных (т.е. Возможно восстановление отказа). Если у вас есть неизменные структуры данных, то, конечно, вы можете иметь исключения, но вы не будете их использовать, потому что когда будете использовать типы сумм. Функциональные структуры данных медленны, поэтому другая альтернатива - иметь чистые функции и делать это на языке без исключения, таком как C, no-except С++ или Rust. Независимо от того, насколько красив D, до тех пор, пока он не очищается от GC и исключений, это не опция.
  • Вы когда-либо проверяли свои исключения, как, например, явный код? А как насчет исключений, которые "никогда не могут произойти"? Конечно, вы этого не делаете, и когда вы на самом деле попадаете в эти исключения, вы ввернуты.
  • Я видел некоторый "красивый" нейтрально-нейтральный код в С++. То есть, он работает оптимально без каких-либо краевых случаев независимо от того, использует ли код, который он вызывает, исключения или нет. Их действительно сложно написать, и я подозреваю, сложно изменить, если вы хотите сохранить все свои гарантии исключения. Тем не менее, я не видел никакого "красивого" кода, который либо бросает, либо ловит исключения. Весь код, который я видел, который напрямую взаимодействует с исключениями, был повсеместно уродлив. Объем усилий, которые приходили на то, чтобы писать нейтральный код, полностью затмевает количество усилий, которые были сохранены от дерьмового кода, который либо бросает, либо ловит исключения. "Красиво" в кавычках, потому что это не настоящая красота: она обычно окаменевшая, потому что для ее редактирования требуется дополнительное бремя поддержания исключительности-нейтральности. Если у вас нет модульных тестов, которые преднамеренно и полностью ошибочно используют исключения для запуска этих краевых случаев, даже "красивый" нейтрально-нейтральный код распадается на навоз.

Ответ 2

В нашем случае мы исключаем исключения через компилятор (например, -fno-exceptions для gcc).

В случае gcc они используют макрос с именем _GLIBCXX_THROW_OR_ABORT, который определяется как

#ifndef _GLIBCXX_THROW_OR_ABORT
# if __cpp_exceptions
#  define _GLIBCXX_THROW_OR_ABORT(_EXC) (throw (_EXC))
# else
#  define _GLIBCXX_THROW_OR_ABORT(_EXC) (__builtin_abort())
# endif
#endif

(вы можете найти его в libstdc++-v3/include/bits/c++config в последних версиях gcc).

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

Если вам нужен какой-то пример, вместо того, чтобы иметь что-то вроде

try {
   Foo foo = mymap.at("foo");
   // ...
} catch (std::exception& e) {}

вы можете сделать

auto it = mymap.find("foo");
if (it != mymap.end()) {
    Foo foo = it->second;
    // ...
}

Ответ 3

Я также хочу отметить, что при запросе об отсутствии исключений существует более общий вопрос о стандартной библиотеке: Используете ли вы стандартную библиотеку, когда находитесь в одном из "мы надеемся" использовать исключения" в лагерях?

Стандартная библиотека тяжелая. В некоторых лагерях "мы не используем исключения", как, например, многие компании GameDev, используются более подходящие альтернативы для STL - в основном на основе EASTL или TTL. Эти библиотеки в любом случае не используют исключения, и потому, что консоль восьмого поколения не справлялась с ними слишком хорошо (или даже вообще). Для передового кода производства AAA исключения в любом случае слишком тяжелы, поэтому в таких случаях это беспроигрышный сценарий.

Другими словами, для многих программистов, исключение исключений происходит в паре без использования STL вообще.

Ответ 4

Примечание. Я использую исключения... но я был вынужден не делать этого.

Вы пытаетесь никогда не вызывать функцию, которая может бросить? (Я не вижу, как это масштабируется, поэтому мне очень интересно услышать, как вы это делаете, если это так)

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

Вы в порядке со стандартной метализацией библиотеки, и вы относитесь к "мы не используем исключения" как "мы никогда не бросаем исключения из нашего кода, и мы никогда не ловим исключения из другого кода"?

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

Вы вообще отключите обработку исключений с помощью компиляторов? Если да, то как работают части, бросающие исключения из стандартной библиотеки?

Это возможно (в тот день, когда он был популярен для некоторых типов проектов); компиляторы делают это/могут поддержать это, но вам нужно будет проконсультироваться со своей документацией о том, какие результаты и могут быть (и какие языковые функции поддерживаются в этих условиях).

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

Общая стратегия для тех, кто "не использует исключения"

Большинство функций имеют набор предварительных условий, которые можно проверить для до вызова. Проверьте их. Если они не выполнены, то не звоните; вернуться к тому, что происходит с обработкой ошибок в этом коде. Для тех функций, которые вы не можете проверить, чтобы обеспечить выполнение предварительных условий... не так много, программа, скорее всего, прервется.

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

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

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

Если используются конструкторы, обычно существуют два подхода, которые используются для обозначения отказа;

  • Установите код внутренней ошибки или enum, чтобы указать сбой и что происходит с ошибкой. Это может быть опрошено после построения объекта и принятия соответствующих мер.
  • Не используйте конструктор (или, по крайней мере, только конструкцию, что не может не сработать в конструкторе - если что-нибудь), а затем используйте метод init() для выполнения (завершения) конструкции. Метод-член затем может вернуть ошибку, если есть некоторая ошибка.

Использование метода init() в целом предпочтительнее, поскольку оно может быть закодировано и масштабируется лучше, чем внутренний код ошибки.

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

Ответ 5

Не пытаясь полностью ответить на заданные вами вопросы, я просто дам Google в качестве примера для базы кода, которая не использует исключения как механизм для устранения ошибок.

В базе данных кода Google С++ все функции, которые могут сбой, возвращают объект status, у которого есть методы типа ok, чтобы указать результат вызываемого абонента.
Они сконфигурировали GCC для отказа от компиляции, если разработчик проигнорировал возвращаемый объект status.

Кроме того, из небольшого открытого исходного кода, который они предоставляют (например, библиотеки LevelDB), похоже, что они не используют STL, так или иначе, поэтому обработка исключений становится редкой. как говорит Титус Уинтерс в своих лекциях в CPPCon, они "уважают стандарт, но не боготворят его".