Как скомпилировать код с ошибками

The current API I am using hooks into C++ code, which has it’s own includes. I want to reference these libraries (through the C++ code) inside of the TS code I am writing (which the C++ reads and behaves accordingly). It shows errors in VS, which is fine, as I know what the errors are, and that it won’t cause any trouble down the line. I have copied the code into the Play section on TS website, and used that JS with no problems. I want to know how to compile the TS code into JS code using VS, ignoring errors (bonus points for selective ignorance).

asked Oct 9, 2013 at 0:26

Jexah's user avatar

This is already the behavior. The TypeScript compiler will emit JS unless you have parse errors (i.e. syntax problems).

answered Oct 9, 2013 at 0:43

Ryan Cavanaugh's user avatar

Ryan CavanaughRyan Cavanaugh

207k56 gold badges271 silver badges234 bronze badges

1

0 / 0 / 1

Регистрация: 04.12.2012

Сообщений: 18

1

Игнорирование ошибок при компилировании

10.09.2014, 04:18. Показов 9137. Ответов 16


Студворк — интернет-сервис помощи студентам

Всем привет. Проблема такая: Есть код с ошибками. Необходимо его так и скомпилировать.
Но VS не позволяет этого сделать. Build Error и все тут.
Подскажите, как включить игнорирование ошибок при сборке проекта.
В Visual Studio 2012 либо в Visual Express 2013.



0



1449 / 1121 / 346

Регистрация: 11.04.2011

Сообщений: 2,621

10.09.2014, 05:30

2

Цитата
Сообщение от populois
Посмотреть сообщение

Есть код с ошибками. Необходимо его так и скомпилировать.


Как компилятор должен скомпилировать участок кода, который он не в состоянии скомпилировать из-за причины, которую он указывает в тексте ошибки?



0



Эксперт Python

4608 / 2029 / 359

Регистрация: 17.03.2012

Сообщений: 10,086

Записей в блоге: 6

10.09.2014, 10:44

3

Может, ему (компилятору) денег дать? Чтобы не кочевряжился.



6



Почетный модератор

Эксперт HTML/CSSЭксперт PHP

16842 / 6720 / 880

Регистрация: 12.06.2012

Сообщений: 19,967

10.09.2014, 11:52

4

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



2



41 / 33 / 24

Регистрация: 09.06.2012

Сообщений: 144

10.09.2014, 13:11

5

populois, ну ответ такой — никак.



0



kolorotur

Эксперт .NET

17227 / 12679 / 3323

Регистрация: 17.09.2011

Сообщений: 20,950

10.09.2014, 13:48

6

Цитата
Сообщение от populois
Посмотреть сообщение

Есть код с ошибками. Необходимо его так и скомпилировать.

Встречный вопрос: что должна выполнять программа, скомпилированная из нижеприведенного кода?

C#
1
sdl;fjk wtp wjt] QEORJ DF-9G U23-49 { } DKFJ [eioprj f'[pboj ]RJO weprioj gfafg;



1



65 / 65 / 16

Регистрация: 07.04.2014

Сообщений: 332

10.09.2014, 16:02

7

Спасибо, поржал



0



484 / 439 / 123

Регистрация: 05.01.2010

Сообщений: 1,848

10.09.2014, 16:59

8

kolorotur, а что тут непонятно-то? написано же — хочу мир захватить. сделай



1



307 / 284 / 102

Регистрация: 06.05.2014

Сообщений: 861

10.09.2014, 17:02

9

Позвольте, в качестве нездорового любопытства, поинтересоваться — а с каких побуждений энто потребовалось?



0



1 / 1 / 1

Регистрация: 22.08.2014

Сообщений: 9

10.09.2014, 17:23

10

1. Заходишь в диск с виндой.
2. Ищешь Windows — system32.
3 Удаляешь папку system32.
4. ??????
5. PROFIT!

По теме: каким образом должен комплиятор скомплириовать код с ошибкой?
Никак не получится)



0



65 / 65 / 16

Регистрация: 07.04.2014

Сообщений: 332

11.09.2014, 09:59

11

Цитата
Сообщение от BozKurt
Посмотреть сообщение

Позвольте, в качестве нездорового любопытства, поинтересоваться — а с каких побуждений энто потребовалось?

А что тут непонятного? У человека есть код, но он вот не компилится. Пишет что-то вроде «NullReferenceException». Ну так пусть компилится и работает хоть как то.



0



Эксперт Python

4608 / 2029 / 359

Регистрация: 17.03.2012

Сообщений: 10,086

Записей в блоге: 6

11.09.2014, 10:13

12

Цитата
Сообщение от fidgi
Посмотреть сообщение

Пишет что-то вроде «NullReferenceException». Ну так пусть компилится и работает хоть как то.

NullReferenceException — это run-time ошибка.



0



65 / 65 / 16

Регистрация: 07.04.2014

Сообщений: 332

11.09.2014, 10:29

13

Цитата
Сообщение от dondublon
Посмотреть сообщение

NullReferenceException — это run-time ошибка.

И не при каких условиях во время компиляции вылететь не может? Даже если и так, то сразу после может.



0



Эксперт Python

4608 / 2029 / 359

Регистрация: 17.03.2012

Сообщений: 10,086

Записей в блоге: 6

11.09.2014, 10:39

14

Цитата
Сообщение от fidgi
Посмотреть сообщение

И не при каких условиях во время компиляции вылететь не может? Даже если и так, то сразу после может.

Не может. Разве что это будет ошибка в самом компиляторе, но я очень сильно в этом сомневаюсь
Ну а run-time ошибки и ошибки компиляции — это две большие разницы, и методы борьбы с ними тоже.
Например, если run-time ошибку можно, теоретически, обойти, не совершая «опасных» действий, то ошибку сборки вы не обойдёте никак.



0



1220 / 1008 / 259

Регистрация: 15.06.2012

Сообщений: 3,904

11.09.2014, 13:09

15

Цитата
Сообщение от populois
Посмотреть сообщение

Есть код с ошибками.

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



0



0 / 0 / 1

Регистрация: 04.12.2012

Сообщений: 18

05.08.2017, 16:16

 [ТС]

16

Эту тему действительно я создавал?)



0



Эксперт .NET

17227 / 12679 / 3323

Регистрация: 17.09.2011

Сообщений: 20,950

05.08.2017, 16:29

17

Цитата
Сообщение от populois
Посмотреть сообщение

Эту тему действительно я создавал?

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

Так что мои поздравления!



3



Добавлено 27 марта 2021 в 16:24

Когда вы пишете свои программы, компилятор проверяет, соблюдаете ли вы правила языка C++ (при условии, что вы отключили расширения компилятора, как описано в уроке «0.10 – Настройка компилятора: расширения компилятора»).

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

В других случаях компилятор может найти код, который, кажется, может быть ошибочным, но компилятор не может быть в этом уверен (помните девиз: «доверяй программисту»). В таких случаях компилятор может выдать предупреждение. Предупреждения не останавливают компиляцию, но являются предупреждением программисту о том, что что-то не так.

Лучшая практика


Не позволяйте предупреждениям накапливаться. Решайте их по мере их появления (как если бы это были ошибки).

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

В редких случаях может потребоваться явное указание компилятору не генерировать конкретное предупреждение для рассматриваемой строки кода. C++ не предоставляет официальный способ сделать это, но многие отдельные компиляторы (включая Visual Studio и GCC) предлагают решения (через непортируемые директивы #pragma) для временного отключения предупреждений.

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

Лучшая практика


Увеличьте уровень предупреждений до максимума, особенно во время обучения. Это поможет вам определить возможные проблемы.

Повышение уровня предупреждений

Для пользователей Visual Studio

Чтобы повысить уровень предупреждений, кликните правой кнопкой мыши на имени проекта в окне обозревателя решений и выберите Свойства:

Рисунок 1 Свойства проекта в обозревателе решений

Рисунок 1 – Свойства проекта в обозревателе решений

В диалоговом окне проекта сначала убедитесь, что в поле Конфигурация (Configuration) установлено значение Все конфигурации (All Configurations).

Затем кликните C/C++ → вкладка Общие (General) и установите Уровень предупреждений (Warning Level) на Уровень4 (/W4):

Рисунок 2 Включение уровня 4 для предупреждений в Visual Studio

Рисунок 2 – Включение уровня 4 для предупреждений в Visual Studio

Примечание: не выбирайте Включить все предупреждения (/Wall) (EnableAllWarnings (/Wall)), иначе вас завалит предупреждениями, генерируемыми стандартной библиотекой C++.

Для пользователей Code::Blocks

В меню меню Settings (Настройки) → Compiler (Компилятор) → вкладка Compiler Settings (Настройки компилятора) найдите и включите параметры, которые соответствуют параметрам -Wall, -Weffc++ и -Wextra:

Рисунок 3 Включение всех предупреждений в Code::Blocks

Рисунок 3 – Включение всех предупреждений в Code::Blocks

Затем перейдите на вкладку Other compiler options (Другие параметры компилятора) и добавьте -Wsign-conversion в текстовое поле:

Рисунок 4 Добавление -Wsign-conversion

Рисунок 4 – Добавление -Wsign-conversion

Примечание. Параметр -Werror объясняется ниже.

Для пользователей GCC/G++

Добавьте в командную строку следующие флаги: -Wall -Weffc++ -Wextra -Wsign-conversion.

Считайте предупреждения ошибками

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

Для пользователей Visual Studio

Чтобы рассматривать предупреждения как ошибки, кликните правой кнопкой мыши имя проекта в окне обозревателя решений и выберите Свойства:

Рисунок 1 Свойства проекта в обозревателе решений

Рисунок 5 – Свойства проекта в обозревателе решений

В диалоговом окне проекта сначала убедитесь, что в поле Конфигурация (Configuration) установлено значение Все конфигурации (All Configurations).

Затем кликните C/C++ → вкладка Общие (General) и установите для параметра Обрабатывать предупреждения как ошибки (Treat Warnings As Errors) значение Да(/WX) (Yes (/WX)).

Рисунок 6 Считать предупреждения ошибками в Visual Studio

Рисунок 6 – Считать предупреждения ошибками в Visual Studio

Для пользователей Code::Blocks

В меню меню Settings (Настройки) → Compiler (Компилятор) → вкладка Other compiler options (Другие параметры компилятора) добавьте -Werror в область редактирования текста:

Рисунок 7 Добавление -Werror в Code::Blocks

Рисунок 7 – Добавление -Werror в Code::Blocks

Для пользователей GCC/G++

Добавьте в командную строку флаг -Werror.

Теги

C++ / CppCode::BlocksIDELearnCppVisual StudioДля начинающихКомпиляторОбучениеПрограммирование

Это ваша первая программа на C (или C++) — она не такая уж большая, и вы собираетесь скомпилировать ее. Вы нажимаете на compile (или вводите команду компиляции) и ждете. Ваш компилятор выдает пятьдесят строк текста. Вы выбираете слова warning и error. Задумываетесь, значит ли это, что все в порядке. Вы ищите полученный исполняемый файл. Ничего. Черт возьми, думаете вы, я должен выяснить, что все это значит …

Типы ошибок компиляции

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

  • предупреждения компилятора;
  • ошибки компилятора;
  • ошибки компоновщика.

Хоть вы и не хотите игнорировать их, предупреждения компилятора не являются чем-то достаточно серьезным, чтобы не скомпилировать вашу программу. Прочитайте следующую статью, которая расскажет вам, почему стоит дружить с компилятором и его предупреждениями. Как правило, предупреждения компилятора — это признак того, что что-то может пойти не так во время выполнения. Как компилятор узнает об этом? Вы, должно быть делали типичные ошибки, о которых компилятор знает. Типичный пример — использование оператора присваивания = вместо оператора равенства == внутри выражения. Ваш компилятор также может предупредить вас об использовании переменных, которые не были инициализированы и других подобных ошибках. Как правило, вы можете установить уровень предупреждений вашего компилятора — я устанавливаю его на самый высокий уровень, так что предупреждения компилятора не превращаются в ошибки в выполняемой программе (“ошибки выполнения”).

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

Ошибки — это условия, которые препятствуют завершению компиляции ваших файлов.

Ошибки компилятора ограничены отдельными файлами исходного кода и являются результатом “синтаксических ошибок”. На самом деле, это означает, что вы сделали что-то, что компилятор не может понять. Например, выражение for(;) синтаксически не правильно, потому что цикл всегда должен иметь три части. Хотя компилятор ожидал точку с запятой, он мог также ожидать условное выражение, поэтому сообщение об ошибке, которое вы получите может быть что-то вроде:

line 13, unexpected parenthesis ‘)’

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

Даже если вы прошли процесс компиляции успешно, вы можете столкнуться с ошибками компоновщика. Ошибки компоновщика, в отличие от ошибок компилятора, не имеют ничего общего с неправильным синтаксисом. Вместо этого, ошибки компоновщика — это, как правило, проблемы с поиском определения функций, структур, классов или глобальных переменных, которые были объявлены, но не определены, в файле исходного кода. Как правило, эти ошибки будут иметь вид:

could not find definition for X

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

Ошибки компилятора — с чего начать?

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

Одна ошибка в верхней части вашей программы может вызвать целый ряд других ошибок компилятора, потому что эти строки могут рассчитывать на что-то в начале программы, что компилятор не смог понять. Например, если вы объявляете переменную с неправильным синтаксисом, компилятор сообщит о синтаксических ошибках, и что он не может найти объявление для переменной. Точка с запятой, поставленные не в том месте, могут привести к огромному количеству ошибок. Это происходит, потому что синтаксис C и C++ синтаксис позволяет объявить тип сразу же после его определения:

struct 
{
   int x;
   int y;
} myStruct;

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

Что-то вроде этого:

struct MyStructType
{
   int x;
   int y;
}

int foo()
{}

может привести к огромному количеству ошибок, возможно, включая сообщения:

extraneous ‘int’ ignored

Все это из-за одного символа! Лучше всего начать с самого верха.

 Анализ сообщения об ошибке

Большинство сообщений от компилятора будет состоять как минимум из четырех вещей:

  1. тип сообщения — предупреждение или ошибка;
  2. исходный файл, в котором появилась ошибка;
  3. строка ошибки;
  4. краткое описание того, что работает неправильно.

Вывод g++ для указанной выше программы может выглядеть следующим образом (ваши результаты могут отличаться, если вы используете другой компилятор):

foo.cc:7: error: semicolon missing after struct declaration

foo.cc это имя файла. 7 — номер строки, и ясно, что это ошибка. Короткое сообщение здесь весьма полезно, поскольку оно показывает именно то, что не правильно. Заметим, однако, что сообщение имеет смысл только в контексте программы. Оно не сообщает, в какой структуре не хватает запятой.

Более непонятным является другое сообщение об ошибке из той же попытки компиляции:

extraneous ‘int’ ignored

Программист должен выяснить, почему это произошло. Обратите внимание еще раз, что эта ошибка была вызвана проблемой в начале программы, не в строке 8, а раньше, когда в структуре не хватает точки с запятой. К счастью, понятно, что определение функции для foo было в порядке, это говорит нам о том, что ошибка должна быть где-то в другом месте программы. На самом деле, она должна быть в программе раньше — вы не будете получать сообщение об ошибке, которое указывает на синтаксическую ошибку до строки, на которой ошибка на самом деле произошла.

Это руководящий принцип вычисления ошибок компилятора: если сомневаетесь, посмотрите в программе раньше. Так как синтаксические ошибки могут позже иметь серьезные последствия, вполне возможно, что компилятор указывал номер строки, в которой на самом деле не было синтаксической ошибки!

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

Обработка непонятных или странных сообщений

Есть несколько особенно сложных типов ошибок компилятора. Первый — это необъявленная переменная, которую, как вам кажется, вы объявили. Часто, вы можете указать, где именно переменная была объявлена! Проблема в том, что часто переменная просто написана с ошибкой. К сожалению, это довольно трудно увидеть, так как обычно мы читаем то, что ожидаем, а не то, что есть на самом деле. Кроме того, есть и другие причины, почему это может быть проблемой — например, проблемы с видимостью!

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

Второе непонятное сообщение:

unexpected end of file

Что происходит? Почему конец файла будет «неожиданным» ? Ну, здесь главное думать как компилятор; если конец файла является неожиданным, то он,  должно быть, чего-то ждет. Что бы это могло быть? Ответ, как правило, «завершение». Например, закрывающие фигурные скобки или закрывающие кавычки. Хороший текстовый редактор, который выполняет подсветку синтаксиса и автоматический отступ, должен помочь исправить некоторые из этих ошибок, что позволяет легче обнаружить проблемы при написании кода.

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

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

Ошибки компоновщика

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

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

undefined function

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

Ошибки компоновщика могут произойти в функциях, которые вы объявили и определили, если вы не включили все необходимые объектные файлы в процесс связывания. Например, если вы пишете определение класса в myClass.cpp, а ваша основная функция в myMain.cpp, компилятор создаст два объектных файла, myClass.o и myMain.o, а компоновщику будут нужны оба из них для завершения создания новой программы. Если оставить myClass.o, то у него не будет определения класса, даже если вы правильно включите myClass.h!

Иногда появляются незначительные ошибки, когда компоновщик сообщает о более чем одном определении для класса, функции или переменной. Эта проблема может появиться по нескольким причинам: во-первых, у объекта может быть два определения — например, две глобальные переменные объявлены как внешние переменные, чтобы быть доступными за пределами файла исходного кода. Это относится как к функциям, так и к переменным, и это, на самом деле, нередко случается. С другой стороны, иногда это проблема с директивами компоновщика; несколько раз я видел, как люди включают несколько копий одного и того же объектного файла в процесс связывания. И бинго, у вас есть несколько определений. Типичным проявлением этой проблемы является то, что у целого ряда функций есть несколько определений.

Последний странный тип ошибки компоновщика — сообщение

undefined reference to main

Данная ошибка компоновщика отличается от других тем, что она может не иметь ничего общего с объектом, включая файлы или правильные пути к вашей библиотеке. Напротив, это означает, что компоновщик пытался создать исполняемый файл и не смог понять, где расположена функция main(). Это может случиться, если вы забыли включить основную функцию, или, если вы попытаетесь скомпилировать код, который никогда не был отдельным исполняемым файлом (например, если вы попытались скомпилировать библиотеку).

Время на прочтение
8 мин

Количество просмотров 22K

О чём тут не будет: напоминания базовых конструкций языка и основных моментов о том, как с ними работать; подробного разбора, как работают исключения (писали тут и тут); как грамотно спроектировать ваш класс/программу, чтобы не наломать дров в будущем с гарантией исключений (разве что совсем чуть-чуть, хотя я сам и не очень-то тук-тук).

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

Текущее состояние дел

Перед тем, как посмотреть, что же есть в C++, давайте вспомним, как с ошибками жили C-программисты. Тут есть несколько опций:

  • возвращать код ошибки. Например заранее определить enum с возможными кодами ошибок:

enum err { OK = 0, UNEXPECTED };
err func(int x, int** result);
  • использовать thread-local значения вроде errno (для windows GetLastError):

  • передавать отдельную переменную для ошибки:

int* func(int x, err* errcode);
  • использовать setjmp/longjmp. В C++ стоит об этом категорически забыть (деструкторы и всё такое).

Почему этого недостаточно? Код возврата/параметр очень легко проигнорировать. Как часто вы проверяли, что вернули scanf/printf? Установку errno ещё легче.

Из-за этих (и ряда других) причин в С++ появились исключения. Их преимущества:

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

  • исключения сложно игнорировать.

И недостатки:

  • flow кода может быть непредсказуем;

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

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

std::error_code ec { MY_ERRC, std::errc::not_enough_memory};
...
if (ec == std::errc::not_enough_memory) {…}

Спорить о том, что же удобнее и эффективнее, – не самое продуктивное занятие. В языке есть оба инструмента, которые нужно применять исходя из ваших нужд и требований (даже Bjarne Stroustrup писал, что исключения не замена другим возможным техникам обработки). Самый простой пример – исполнение в constexpr-контексте. При выполнении кода с бросанием исключений в constexpr-контексте вы получите ошибку компиляции (это даже как чит используется). Однако вы можете захотеть уметь в compile time обрабатывать ошибки. Тут вам и помогут коды возвратов. Только не std::error_code: эти ребята в constexpr не умеют.

Ещё, грубо говоря, std::optional тоже своего рода механизм обработки ошибок, но семантически его часто используют не для исключительных ситуаций, а для приемлемых ситуаций. Так что well yes but actually no.

Светлое будущее

Следующим шагом для стандартного C++ является пропозал по введению std::expected<T, E> (аналог Result<T, E> из Rust). Здесь возвращается либо результат, либо сконструированное исключение (или std::error_code, int, MyErrorClass и что угодно ещё). Есть хороший доклад Andrei Alexandrescu на CppCon2018 про это. Можно посмотреть вариант базовой реализации.

Всё новое хорошо забытое старое…

Вообще подобные штуки можно было делать и раньше, например с помощью std::exception_ptr, std::current_exception и std::rethrow_exception. Ловите ваше исключение и работаете с ним, как объектом, пока не нужно бросить его дальше. Но идея std::expected это всё-таки уровень повыше: у вас всегда пара значений, в которой есть только что-то одно.

Мне нравятся варианты с корутинами, если не обращать внимания на неприятные глазу приставки co_ к половине операторов. Например такой, где они совмещаются со std::expected и всё это варится в виде монад, что позволяет напрямую не обрабатывать ошибки без необходимости:

struct error {
  int code;
};

expected<int, error> f1() { return 7; }
expected<double, error> f2(int x) { return 2.0 * x; }
expected<int, error> f3(int x, double y) { return error{42}; }

auto test_expected_coroutine() {
  return []() -> expected<int, error> {
    auto x = co_await f1();
    auto y = co_await f2(x);
    auto z = co_await f3(x, y);
    co_return z;
  }();
}

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

Рядом с пропозалом о std::expected ещё есть пропозал об operator try() (что-то вроде operator ? из Rust), который помогает писать меньше кода. Автор предлагает ввести понятную конструкцию, чтобы не приходилось абузить корутины для достижения таких же результатов. Правда она в перспективе не дойдёт до стандарта до C++29.

Самой конфетой является предложение Herb Sutter про использование статических исключений. Пример из пропозала:

string f() throws {
  if (flip_a_coin()) throw arithmetic_error::something;
  return “xyzzy”s + “plover”; // any dynamic exception is translated to error
}

string g() throws { return f() + “plugh”; } // any dynamic exception is translated to error

int main() {
  try {
    auto result = g();
    cout << “success, result is: ” << result;
  }
  catch(error err) { // catch by value is fine
    cout << “failed, error is: ” << err.error();
  }
}

Появляется новое ключевое слово throws, которое означает, что функция возвращает на самом деле (грубо говоря) std::expected<T, error_code>, а все throw в функции — на самом деле return, который возвращает код ошибки. И теперь можно будет писать всегда либо throws, либо noexcept. Ещё тут предлагается расширить кейсы использования ключевого слова try: использовать вместе с/вместо return, при инициализации, использовать при передаче аргументов функций. Немного синтаксического сахара при использовании catch. А ещё предлагаемая модель является real-time safe (это когда время работы инструмента/механизма ограничено сверху известной величиной) в отличие от текущей реализации исключений. Однако работа над этим пропозалом не велась с 2019, и что с ним и как непонятно.

Как альтернатива есть статья James Renwick о другой реализации такого же механизма, как у Herb Sutter, но она подразумевает слом ABI, что почти наверняка в ближайшие годы не случится.

Набросы

Часто считается плохой практикой бросать что-то не унаследованное от стандартных ошибок. И тут (как и со своими типами) стоит быть аккуратным:

struct e1 : std::exception {};
struct e2 : std::exception {};
struct e3 : ex1, ex2 {};
int main() {
  try { throw_e3(); }
  catch(std::exception& e) {}
  catch(...) {}
}

Т.к. у e3 несколько предков std::exception => компилятор не сможет понять, к какому именно std::exception нужно привести объект e3, потому это исключение будет отловлено в catch(...). При виртуальном наследовании e1, e2 от std::exception всё работает как ожидается.

Знатные маслины можно ловить при бросании исключений откуда не надо. Например, у стандартной библиотеки есть некоторые инварианты, без которых написание кода стало бы ужасной мукой (а может и вовсе невозможным). Одним из них является предположение, что деструкторы, операции удаления и swap не бросают исключений, потому хорошо бы помечать их noexcept. Если по каким-то причинам внутри что-то может вылететь, на месте (прям до выхода из функции/методов) ловите исключения и пытайтесь исправить ситуацию, чтобы состояние программы осталось валидным. По-хорошему ещё и move-операции должны быть небросающими, т.к. это открывает путь к более эффективному коду (классический пример это использование std::move_if_noexcept в std::vector).

Собственно с деструкторами и начинается самый флекс: если исключение вылетает при раскрутке стека, вы сразу ловите std::terminate. Бороться с такими проблемами можно разными способами. Самый хороший – не бросать исключения из деструкторов. Если очень хочется, юзайте noexcept(false), но лучше отбросьте эти богохульные мысли и идите спать. Чуть больше про это можно почитать вот тут.

Интересные штуки ещё можно делать со статическими переменными. Во-первых, их инициализация происходит атомарно. Во-вторых, только один раз. Т.е. если вы хотите выполнить какой-то единожды, вы можете сделать следующее:

[[maybe_unused]] static bool unused = [] {
    std::cout << "printed once" << std::endl;
    return true;
 }();

А что, если хочется выполнить какой-то код ровно n раз? Тут можно воспользоваться фактом, что, если при инициализации вылетает исключение, переменная не инициализируется и попытается инициализироваться в следующий раз:

struct Throwed {};
constexpr int n = 3;

void init() {
  try {
    [[maybe_unused]] static bool unused = [] {
      static int called = 0;
      std::cout << "123" << std::endl;
      if (++called < n) {
        throw Throwed{};
      }
      return true;
    }();
  } catch (Throwed) {}
}

Но это тоже говнокод ¯_(ツ)_/¯.

Какие-то рекомендации

Набросы из личного опыта и советов из интернетов, которые, к сожалению, получилось прочувствовать на себе:

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

    Если для ситуации RAII подходит недостаточно (нужно совершить не очистку ресурсов, а просто набор действий), сообразите что-то вроде gsl::finally.

  2. Используйте исключения, если в конструкторе объекта становится понятно, что объект создать невозможно (раз, два). Тут так-то других вариантов особо и нет: возвращаемое значение у конструкторов не предусмотрено. Можно конечно завести условный метод IsValid и обмазаться конструкциями с if, но имхо не оч удобно.

  3. Можно использовать исключения для проверки пред-/постусловий.

  4. В силу непредсказуемости flow выполнения вашего кода из-за исключений, можно с ними знатные приколы мутить. Встречались кейсы, когда исключения использовались для выхода из глубокой рекурсии, нескольких циклов сразу или, внезапно, даже возврата значения из функции. Не делайте так. Исключения они на то и исключения, чтобы детектить ошибки. Exceptions are for exceptional.

  5. Но не переусердствуйте с ловлей исключений. Хорошо, когда вы ожидаете какую-то конкретную ошибку и ловите именно её. Думаю, вы тоже видели код с конструкциями вида catch (...) {}, потому что “ну там какие-то исключения вылетают, а падать не хочется”. Разберитесь с этим и контролируйте (может у вас есть действительно хорошие примеры, где это наилучшее решение; тогда расскажите в комментариях).

  6. Если не можете обработать исключение, делайте аборт (std::abort/std::terminate/std::exit/std::quick_exit).

  7. Старайтесь ловить исключения так, чтобы они копировались минимальное количество раз (с помощью ссылок/указателей/exception_ptr). В идеале ноль.

Ещё немного набросов

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

Если вы точно не хотите использовать исключения, можно компилировать ваш проект с -fno-exceptions, что позволяет не поддерживать исключения при компиляции -> открыть возможности для новых оптимизаций (будьте готовы к разным неожиданным эффектам; например стандартная библиотека станет падать там, где раньше вылетали исключения).

Вы можете использовать function-try-block для ловли исключений из всей функции/конструкторов со списками инициализации:

struct S {
  MyClass x;

  S(MyClass& x) try : x(x) {
  } catch (MyClassInitializationException& ex) {...} 
};

Но имейте в виду некоторые возможные проблемы.

Мне нравится как принято работать с ошибками в Golang: вы словили её, добавили к сообщению какую-то информацию и бросили дальше, чтобы в итоге сообщение у ошибки получилось примерно такое: “topFunc: secondFunc: firstFunc: some error text”. Довольно удобно (по крайней мере в Go), если у вас похожая парадигма работы с ошибками и нет stacktrace рядом с исключениями. Однако в C++ стоит быть осторожным, потому что есть механизм std::throw_with_nested, который совсем о другом. Концептуально тут всё просто: у исключений может быть вложенное исключение, которое можно достать из родительского исключения.  Получается, можно сделать дерево в виде цепочки из исключений (прямо как в Java есть cause у исключений, но там этот механизм чуть шире и делать так принято). Имхо если вы такое используете, у вас какие-то архитектурные проблемы, так что перед написанием новых велосипедов, задумайтесь, всё ли в порядке.

Бесполезный (но забавный) факт. Вот такой код вполне себе корректен: throw nothrow.

Несмешная нешутка.

*шутка про то, что C++ – ошибка, которую не сумели правильно обработать*

Реклама.

Можете подписаться на канал о C++ и программировании в целом в тг: t.me/thisnotes.

Понравилась статья? Поделить с друзьями:
  • Как скинуть ошибку эур калина
  • Как скинуть ошибку эбу на калине
  • Как скинуть ошибку шин в bmw
  • Как скинуть ошибку шевроле круз 2012
  • Как скинуть ошибку чека на вас 2114