Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / Исключения в C++ (5 стр)

Исключения в C++ (5 стр)

Страницы: 14 5 6 7 8 Следующая »
skalogryzУчастникwww16 мая 20185:11#60
Sbtrn. Devil
> Checked Exceptions Might Have Their Place, But It Isn't In Java
цитата хорошая
In fact, the general consensus among in-the-trenches Java programmers is that dealing with checked exceptions is nearly as unpleasant a task as writing documentation.

как писателю на не С++, ответственно заявляю, что отлавливать exception-ы из других языков - грусть и печаль. (об этом было сказано выше)
По-этому писать библиотеки лучше без exception-ов.

Правка: 16 мая 2018 5:13

Ghost2Постоялецwww16 мая 20188:10#61
Sbtrn. Devil

Ну в Java хотя бы можно как-то соблюдение соглашения гарантировать. А вот на все эти noexcept компилятор даже ворнинга по умолчанию не выдаст. Все же тут говорят про то, что исключения нужны чтобы клиент мог что-то там исправить. Так это возможно по хорошему только с checked исключениями. Иначе лучше fail_bit какой-нибудь завести.

MrShoorУчастникwww16 мая 20188:19#62
skalogryz
Ты когда Duby доделаешь?
DelfigamerПостоялецwww16 мая 201812:17#63
Ghost2
У меня такое чувство, будто мы говорим о разных вещах.
Лично я используя исключения затем, чтобы прервать операцию, которую невозможно завершить.
Та же сериализация, например, - если закончилось место на диске, или пользователь вынул флешку, или сетевой кабель посреди передачи отрубили, или ещё по какой-то причине запись невозможна - нет смысла пытаться записать что-то ещё, поэтому я поднимаю исключение и прерываю всю операцию.
Если бы я делал это через возврат - мне бы пришлось каждый вызов подфункции обёртывать в if(!dostuff(...)) { return false; }, что по сравнению с настоящими исключениями является лишним кодом, не несущим существенной смысловой нагрузки.
Если же "провал" является значимым результатом - например, вызов pop() на пустом контейнере - то этот результат именно возвращается в виде значения, с которым затем работает дальнейший код.
Таким образом, в checked-исключениях в принципе не возникает необходимости - те результаты, о которых сообщают они, передаются в виде обычных значений; тогда как действительные исключения бросаются там, где восстанавливать уже нечего, можно только написать в лог и, если операция некритична для работы всей программы - завернуть исключение где-нибудь наверху и пойти дальше обрабатывать сообщения.

В качестве ещё одного примера могу привести парсер скриптового языка. Это top-down парсер, который принимает на вход поток токенов. Правила работы основаны на PEG - каждая функция представляет собой правило, которое применяется к потоку на определённой позиции, и в общем случае способна выдать три вида результатов:
- если исходный текст успешно парсится правилом - поглощённые токены убираются из потока, а функция возвращает AST распарсенной конструкции,
- если исходный текст не парсится текущим правилом, но может обозначать какую-то другую синтаксическую конструкцию - поток остаётся на месте, а функция возвращает null,
- наконец, если становится ясно, что исходный текст содержит ошибку - функция поднимает исключение с описанием ошибки.
По ссылке - функция, которая парсит унарные выражения. Соответствующее грамматическое правило выглядит как

expr.unary := expr.unary.prefix* expr.element;
expr.unary.prefix := "+" / "-";
expr.element :=
  expr.element.number / /* 123.45e-3, 0x452.21p5 */
  expr.element.string / /* 's-quote string', "d-quote string", [[long string]] */
  expr.element.nil /
  expr.element.name / /* x, print, math.sqrt */
  expr.element.subexpression / /* (2+3*x - math.sin(y)) */
  expr.element.function / /* function[x:number]:number return x*x end */
  expr.element.type; /* type function[a:number, b:number]:number, type (3*x - math.cos(y)) */
Язык построен так, что после унарного префикса обязательно должно идти выражение. В expr_unary это учитывается и в случае, если был считан хотя бы один префикс, но операнд распарсить не удалось - парсер досрочно закрывается, и пользователю выдаётся сообщение "expression expected" со ссылкой на позицию, где именно в тексте ошибка.

skalogryz
> По-этому писать библиотеки лучше без exception-ов.
У меня в коде на границе языков генерируются обёртки, которые в том числе ловят исключения из одного языка, аккуратно передают через структуру, и затем распаковывают на принимающей стороне.
Так что отсутствие явных исключений в интерфейсе - ещё не причина отказываться от них во внутреннем коде библиотеки.
У libpng вон, например, внутри вообще всё через longjmp делается, но это не мешает использовать его из плюсов.

inb4 какой-нибудь иннуэнда подкатывает с "longjmp лучше исключений"
DelfigamerПостоялецwww16 мая 201812:30#64
В общем, исключения, о которых говорю я - это не замена return false на goto не пойми куда; а замена exit() на то же самое, только в меньшем масштабе и поддающееся контролю.

Правка: 16 мая 2018 12:30

Sbtrn. DevilПостоялецwww16 мая 201812:35#65
skalogryz
> In fact, the general consensus among in-the-trenches Java programmers is that
> dealing with checked exceptions is nearly as unpleasant a task as writing
> documentation.
2006 год. С тех пор случилось многое, в том числе жаба-8, и проверяемые эксепшоны успели проклясть даже сами авторы жабы.

> как писателю на не С++, ответственно заявляю, что отлавливать exception-ы из
> других языков - грусть и печаль.
А это из-за фатальной ошибки в дизайне - в Ц++ не встроили возможность кустомизировать имплементацию эксепшенов (как, например, можно кустомизировать operator new). Хотя доступных из языка средств, чтобы это сделать, хватает, начиная с setjmp и заканчивая ОС-специфическими фокусами.

Ghost2
> Ну в Java хотя бы можно как-то соблюдение соглашения гарантировать.
Можно-то оно можно, но за это приходится платить неоправданным геморроем и хрупкостью интерфейсов. В принципе, возможен и альтернативный подход.

skalogryzУчастникwww16 мая 201814:08#66
MrShoor
> Ты когда Duby доделаешь?
отчёт в личку прислал, чтобы оффтоп не разводить

Правка: 16 мая 2018 14:08

МизраэльПостоялецwww16 мая 201816:48#67
Delfigamer
> Основная логика исключений в том, что программист обрабатывает ошибка там, где
> с ними можно справиться.
для того, чтобы обработать ошибку, ты должен представлять себе весь спектр исключений, которые могут возникнуть в любой точке программы. Вот два примера:
HRESULT Foo1()
{
  ...
  if (..) return E_FAIL;
  return S_OK;
}

void Foo2()
{
  ...
  if (..) throw FooFailed();
}

и вызывающая функция:

void Bar1()
{
   hr = Foo1();
}

void Bar2()
{
  try
 {
      Foo2();
 } catch (???)
 }
}

С функцией Bar1 всё понятно, есть конкретный перечень возможных ситуаций при вызове Foo1. В функции Bar2 никакой определённости нет, что ты напишешь в catch, при условии, что писать  catch (...) нельзя (почему нельзя, поясню позднее). В розовом мире С++ программистов, нужно просто заглянуть в документацию Foo2 и прочитать, какие исключения она может выбросить, но проблема в том, что Foo2 писали реальные люди, которым свойственно ошибаться, и часть исключений может прилететь из внутренних функций Foo2. Т.е. в общем случае сказать какие исключения возможны после вызова Foo2 просто невозможно, а пропустив одно из неучтённых мы просто закрашим приложение.

DelfigamerПостоялецwww16 мая 201816:51#68
Мизраэль
> для того, чтобы обработать ошибку, ты должен представлять себе весь спектр
> исключений, которые могут возникнуть в любой точке программы.
Возможно, но не у меня.
Delfigamer
> действительные исключения бросаются там, где восстанавливать уже нечего, можно
> только написать в лог и, если операция некритична для работы всей программы -
> завернуть исключение где-нибудь наверху и пойти дальше обрабатывать сообщения.
MrShoorУчастникwww16 мая 201820:20#69
Delfigamer
> Если бы я делал это через возврат - мне бы пришлось каждый вызов подфункции
> обёртывать в if(!dostuff(...)) { return false; }, что по сравнению с настоящими
> исключениями является лишним кодом, не несущим существенной смысловой нагрузки.
Так себе подход, потому что false вообще не говорит но о чем. Когда у тебя будет 5 вызовов вглубь, а самая верхняя функция вернет тебе false, все, что ты можешь сделать - сказать пользователю: "Ну х** знает. Какая-то ошибка".

> Таким образом, в checked-исключениях в принципе не возникает необходимости - те результаты, о которых сообщают они, передаются в виде обычных значений
Возникают. У тебя есть например куча функций:
void send(Socket socket, Packet1 p);
void send(Socket socket, Packet2 p);
void send(Socket socket, Packet3 p);
и в каждой из них может произойти ошибка, например сокет будет закрыт сервером. Какие у нас есть варианты?
1. Уронить приложение. Решение очевидно бредовое. Это всего лишь дисконнект от сервера, а ты роняешь приложение.
2. Протащить код ошибки наружу. Не самое удачное решение. Сам send очевидно что не может сделать правильный реконнект. Но и тот, кто вызывает send с большой вероятностью не может это сделать. Место, хоть где как-то можно обработать эту обшику может оказаться на 5-10 уровней по стеку выше. И во всех эти функциях надо будет возвращать код ошибки в духе "ERR_DISCONNECTED". А самая неприятная ситуация может случится, когда у тебя функция, вызывающая send еще например работает с файлом, и вот ты уже не можешь взять и вот просто так написать эту функцию:

int foo() {
  File f;
  int err = open_file(filename, f); //тут тоже может быть ошибка, типа file not found, или ошибка доступа
  if (err != NO_FILE_ERROR) return err; //это типа файловая ошибка
  Packet1 p(f);
  int err = send(p);
  if (err != NO_SOCKET_ERROR) return err; //а это ошибка сокета
}

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

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

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

void CheckError(ErrorCode err, ErrorCode no_error = 0) {
  if (err == no_error) return;
  LogBackTrace('crash.log');
  exit(-1);
}
И оборачиваешь все вызовы, которые могут зафейлится:
CheckError(OpenFile(filename, f));
Делов то.

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

DelfigamerПостоялецwww16 мая 201822:06#70
MrShoor
> Так себе подход, потому что false вообще не говорит но о чем. Когда у тебя
> будет 5 вызовов вглубь, а самая верхняя функция вернет тебе false, все, что ты
> можешь сделать - сказать пользователю: "Ну х** знает. Какая-то ошибка".
Ну, имелась в виду разница между
return error;
и
throw error;
Чтобы, собственно, показать, что
MrShoor
> Исключения нужны, чтобы не тянуть код ошибки через 100500 вызовов руками.
и что аргумент "throw это goto неизвестно куда" - это фигня и провокация.
MrShoor
> try catch на конкретный тип исключения
Сложна. Проще завалить всю операцию и выдать пользователю окошко с текстом ошибки, с расчётом на то, что если будет надо - он исправит косяк и нажмёт кнопку ещё раз.
Да и вообще,
MrShoor
> > Таким образом, в checked-исключениях в принципе не возникает необходимости -
> > те результаты, о которых сообщают они, передаются в виде обычных значений
> Возникают.
Ты приводишь аргументы в пользу исключений вообще (с чем я полностью согласен), тогда как я привожу одну из причин отказа от присовывания исключений к интерфейсу - чем checked exceptions и выделяются.
A checked exception is a type of exception that must be either caught or declared in the method in which it is thrown.

Если для запроса отрицательный результат считается нормальной ситуацией, которую обязательно нужно опознать и отработать - лучше передать это стандартными для результатов средставами (return или out-аргументы), а исключения оставить для ненормальных ситуаций, когда реактор горит и о результатах уже нет и речи.
MrShoor
> exit(-1);
Так это,
Delfigamer
> если операция некритична для работы всей программы - завернуть исключение
> где-нибудь наверху и пойти дальше обрабатывать сообщения
exit() не заворачивается же. У нас ведь всего лишь дисконнект от сервера, не надо так уж сразу ронять всё приложение.

Я не говорю, что мой способ применения исключений - самый лучший; я говорю, что для моей задачи (прервать невыполнимую операцию) throw/catch - самый удобный вариант, потому что
- return/if многословеный,
- exit крашит больше, чем нужно,
- longjmp/setjmp, ясен пень, течёт из всех щелей и вообще, вот ему-то внутри плюсов действительно места нет.

Правка: 16 мая 2018 22:10

MrShoorУчастникwww16 мая 201822:37#71
Delfigamer
> Сложна.
Что сложно то? Сделать в нужном месте catch по типу исключения?

> Проще завалить всю операцию и выдать пользователю окошко с текстом ошибки
После AV на write тоже будешь показывать пользователю окошко, и дашь ему возможность продолжить?

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

Да и просто показать пользователю сообщение об ошибке может быть недостаточно. Вот ты играешь в онлайн игру. Зашел в лобби, нажал найти матч, ждешь, когда система тебе подберет противников. Тут внезапно дисконнект от сервера. Показать пользователю сообщение? Ок. Но ведь ты в лобби, и по хорошему, тебя надо выкинуть из него (а еще лучше, показать иконку переподключения и переподключать автоматически). Т.е. в нормально приложении затачивать логику под исключения так или иначе придется.

Ghost2Постоялецwww17 мая 20180:42#72
Delfigamer

> это фигня и провокация
Ты вот один пишешь, у тебя все хорошо со знанием того, что где бросается. Даже в одном месте memcpy в try завернут. Но даже в твоём случае все try завершаются catch(...).

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

Правка: 17 мая 2018 0:42

skalogryzУчастникwww17 мая 20183:49#73
MrShoor
> ашел в лобби, нажал найти матч, ждешь, когда система тебе подберет противников. Тут внезапно дисконнект от сервера....
> Т.е. в нормально приложении затачивать логику под исключения так или иначе придется.
а с каких пор, дисконнект от сервера (по любой причине) это исключение?
для сетевого программирования это штатная ситуация. (на ровне с подключением или получением сообщения... исключениями же они не обрабатываются. Тоже ведь могли бы)
Зачем из штатной ситуации (логики) делать исключение?

Внештатная ситуация - AV или Out of Memory. (Которых не может быть с точки зрения программирования микроконтроллера, какого-нибудь; но вполне внештатная для игры под ПК).
Например, при сохранении файла - Out of Disk Space, по-идее нифига не внештатная ситуация, и ей не следует быть исключением.
А при работе с временным файлом (кто-нить в век 64-битности работает с temp файлами?) - это вполне может быть исключением (на уровне Out of Memory). Опять же может и не быть, если предусмотрен механизм удаления темп файлов (своих естественно).

Правка: 17 мая 2018 3:53

MrShoorУчастникwww17 мая 20183:59#74
skalogryz
> а с каких пор, дисконнект от сервера (по любой причине) это исключение?
Дисконнект от сервера - это обычный код ошибки от сокетной функции. Но при получении такой ошибки удобно бросить исключение, а не тянуть через тридевять земель этот самый код ошибки в явном виде.
Исключение - это не все, конец, приехали. Исключение - это то, что обычно не происходит, но иногда случается. Оно и называется поэтому исключение, а не всеконецприехали.
Страницы: 14 5 6 7 8 Следующая »

/ Форум / Программирование игр / Общее

2001—2018 © GameDev.ru — Разработка игр