LINUX.ORG.RU

exceptions в C++

 ,


5

9

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

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

UPD:
Из любопытного
почитать (стр. 32)
посмотреть

★★★★★

Последнее исправление: morse (всего исправлений: 1)

Ответ на: комментарий от eao197

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

kawaii_neko ★★★★
()
Ответ на: комментарий от RazrFalcon

Т.е. программист, читающий код, если заметит ?, поймет лишь, что здесь будет прервано исполнение и что-то будет возвращено. Но что именно просто так не определить.

Это все хорошо, для такого стиля кодирования даже целый язык сделали. Остается только вопрос: чем все это поможет в C++?

eao197 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Просто - это как?

Глядя на фрагмент кода.

Ничем. Я о плюсах ничего и не говорил.

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

eao197 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Как вы себе это представляете? Явно указывать тип ошибки?

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Как говориться, у кого какие проблемы...

eao197 ★★★★★
()
Ответ на: комментарий от eao197

на сишных функциях? они не генерируют ни сигнал, ни исключение.

А так, смысл такой: сделать класс-обёртку, который при успешном чтении (формат прочитанных данных совпадает с ожидаемым) генерирует один сигнал, а при ошибочном — другой. При некритичной ошибке (warning) ещё один сигнал, ортогональный предыдущим. В случае ошибки, fopen() посылает сигналы о 1) собственно факте произошедшей ошибке 2) сигналы в соответствии характеру ошибки: ошибки доступа\отсутствие файла\битые данные и т.п.

Важно, что обработчик успешного чтения — это отдельная функция.

далее, при необходимости, эти сигналы можно назначать слотам или игнорировать

next_time ★★★★★
()
Ответ на: комментарий от next_time

сигналы qt — это не костыль, а более универсальный инструмент, ещё и более быстрый

у вас qt головного мозга

anonymous
()
Ответ на: комментарий от RazrFalcon

Ну да, т.е. тип соединения по-умолчанию. Остальные типы соединения нужны для работы с многопоточностью, либо для обработки некоторых специальных случаев и ( в обоих случаях) дают функциональность, которую исключения С++ 03 вообще не позволяют.

С++11 позволяет пробрасывать исключения через потоки, но, честно говоря, я ещё не пробовал эту фичу и её скорость работы надо ещё тестить.

next_time ★★★★★
()
Ответ на: комментарий от next_time

Интересно, как вы это выражали бы в коде. Вот так, может быть?

signal_set open_result_handlers;
open_result_handlers.connect(open_file::success, [&](FILE * f){...});
open_result_handlers.connect(open_file::file_not_found, [&](const string & name){...});
open_result_handlers.connect(open_file::io_error, [&](const string & name, int error){...});
open_file::open("some_file.txt", "rb", open_result_handlers);

eao197 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Это же просто парсер на макросах.

При том, что там есть комбинаторы, определенные для типов сумм, в частности result.

Freyr69 ★★★
()
Ответ на: комментарий от eao197

optional<FILE *> f = open_file(«some_name.txt», «rb»);
fread(buf, 1, buf_size, *f);

The behavior is undefined if *this does not contain a value.

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

Freyr69 ★★★
()
Ответ на: комментарий от Freyr69

Такой optional — совсем не optional, какой в нем вообще смысл, мол, смотрите, мы тоже пытаемся нагородить вменяемую систему типов как в мл/аде (на самом деле нет)?

Не понял суть претензии. Есть вот такой optional. Вроде как в operator* для него дополнительная проверка не делается, т.к. основной сценарий его использования вот такой:

optional<FILE*> f = open_file(...);
if(f)
  fread(buf, 1, buf_size, *f);
Т.е. при правильном использовании optional вторая проверка не нужна. Если бы ее добавили в реализацию optional, то это были бы лишние накладные расходы.

Кому не нравится такое опасное поведение, может сделать свой optional или нашлепку к стандартному, что-то вроде:

template<typename T> auto safe_get(const optional<T> & o) {
  if(!o) throw ...; // Or even std::abort().
  return *o;
}
...
optional<FILE*> f = open_file(...);
fread(buf, 1, buf_size, safe_get(f));
Или какой-то аналог ПМ на лябмдах:
template<typename T, typename L>
void handle_optional(const optional<T> & opt, L && l) {
  if(opt)
    l(*opt);
}
...
optional<FILE*> f = open_file(...);
handle_optional(f, [](auto file) {
    fread(buf, 1, buf_size, file);
  });

C++ — он же не про безопасность, а про возможность извлечь максимум, пусть даже и ценой отстрела ног.

Они б хоть исключение кидали, если у них есть функция-распаковщик.

Так если мы не отказываемся от исключений, какой смысл колупаться с optional, result, expected и пр. потугами сделать из C++ подобие ML-я?

eao197 ★★★★★
()

В конфигурациях где раскрутка стека основана на отладочной информации (на почти любом linux-elf) они (try/catch) якобы имеют zero cost в случае отсутствия exceptions.

Отрицательный эффект - больший размер исполнямых файлов (и memory footprint, т.к. отладочная информация нужна во время исполнения). Медленная раскрутка стека в случае exception - фактически будет работать виртуальная машина, выполняющая dwarf2 инструкции. Сюрпризы, если между throw и catch есть frame от кода, скомпилированного без поддержки исключений. Сложный runtime - как самолет на фоне телеги, если сравнивать eh код со всем остальным платформенным кодом.

anonymous
()
Ответ на: комментарий от next_time

то что вы описываете - немного о другом.

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

T a_which_may_fail(resource1& r1, resource2& r2) throw(some_object_with_info_about_error)
{
   resource0 r0(r1,r2);//освобождается автоматически 
   if (fail) throw some_object_with_info_about_error;
   return some_result;
}
G g(resourceN* n) throw(some_object_with_info_about_error) 
{
  resource1 r1(...);
  resource2 r2(...);
  T a = a_which_may_fail(r1,r2);
  ...
  return some_other_result;
}
int f() {
   resourceN rn(...);
   try {
      G g = g(&rn);
      ...
      work;
      return SUCCESS;
   } catch(const some_object_with_info_about_error& e) {
      //явная проверка на ошибки и обработка
      handle_error;
   }
}
Исключение в сочетании с RAII заменяет нам проверки кодов ошибок при вызове вложенных функций и позволяет автоматически освободить те ресурсы, которые были выделены для проведения операции, которая может fail, после чего вернуть управление в ту точку, где мы можем обработать ошибку. В сочетании с указанием типов кидаемых исключений позволяет нам не проскочить изза ошибки программиста нашу явную проверку. Но не более того!

Это не обработка ошибок, а процесс сообщения об ошибке коду который начнет обработку ошибки. Исключение это такой mega_return.

Представьте себе что у нас есть какой-нибудь язык Х++, где исключений нет, а есть оператор mega_return возвращающий произвольный объект на много уровней назад, пока его не станет возможно записать в какую-нибудь переменную. Кроме того любую переменную можно объявить как T t и T? t. T? позволяет положить в себя как Т так и ссылку на объект другого типа, а потом проверить тип того, что же там вернули:

T? a_which_may_fail(resource1& r1, resource2& r2)
{
   resource0 r0(r1,r2);//освобождается автоматически 
   if (fail) mega_return some_object_with_info_about_error;
   return some_result;
}
G? g(resourceN* n)
{
  resource1 r1(...);
  resource2 r2(...);
  T a = a_which_may_fail(r1,r2);
  ...
  return some_other_result;
}
int f() {
   resourceN rn(...);
   G? g = g(&rn);
   switch (g) {
   case some_object_with_info_about_error:
      some_object_with_info_about_error& error = g;
      //явная проверка на ошибки и обработка
      handle_error;
      return;
   default:
      work;
      return SUCCESS;
   }
}
это то же самое только мы не используем исключение, а просто возвращаемся на много уровней назад, пока нам не удастся сохранить результат mega_returnа.

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

давайте не будем путать процесс сообщения об ошибке и процесс обработки ошибок.

i36_zubov
()
Ответ на: комментарий от anonymous

ну не на отладочной же.

отладочные данные в другой секции лежат.

i36_zubov
()
Ответ на: комментарий от eao197

мне кажется что разговор куда-то не туда зашел.

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

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

Причина же в том, что люди пишут как на жабе, поэтому у них

Сюрпризы, если между throw и catch есть frame от кода, скомпилированного без поддержки исключений

Для них это «сюрпризы», хотя этот код(на Си или ObjC например) им не марсиане подсунули, и вызов из него и в него происходит не потому сигналу датчика случайных чисел, а потому что кто-то так написал код. Не думая, как он привык это делать в жабе.

i36_zubov
()
Ответ на: комментарий от anonymous

Спасибо.

Тем более, у разработчика есть выбор: optional::operator*() — быстро, но небезопасно и optional::value() — безопасно, но не быстро.

eao197 ★★★★★
()
Ответ на: комментарий от Freyr69

Такой optional — совсем не optional

Настолько же, насколько vector - не массив. И там и там есть быстрый интерфейс, и есть безопасный.

anonymous
()
Ответ на: комментарий от i36_zubov

а то что пишете вы - это уже процесс обработки сообщения об ошибке.

Ничего подобного, описанный мною алгоритм следующий: увидев ошибочную ситуацию — кинуть сигнал. Опционально, завершить работу текущего слота.

Где здесь вы видите обработку ошибок? Только сообщения о них.

anonymous
()
Ответ на: комментарий от i36_zubov

Я понимаю о чем вы говорите и я написал одно слово о бесплатности error free пути - «якобы». Вот пара аргументов и точек зрения на предмет.

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

Далее, есть хорошая аналогия с ядром linux - копирование памяти процесса ядром от имени процесса.с Код не проверяет наличие и корретность трансляции страниц, просто корирует. А на page fault - смотрит что же случилось. Там это сделано просто и здорово, с пониманием. Ключевой момент - отгрузка проверок на mmu. За счет этого happy path - быстрее чем с проверками. Вот и пользовательском коде - проверка error condition, если будет, должна быть аппаратной? Иначе она будет в другом месте и будет стоить столько же или больше. А если ее не будет - значит, проверка ленивая, тогда и изначальное действие ленивым можно делать. А аппратная проверка в пользовательском код (при том что это в общем-то только провека на корректность указателя - т.е. прямая применимость ограничена) - это в рамках обсуждаемой парадигмы будет throw из обработчика segfault. Это работает на linux, но это, наверное, не портируемо. И там баги временами то тут то там (без шуток.). Случай-то дурацкий. Находятся эти баги, кстати, не в с++ приложениях, а в приложениях, которые thread cancellation используют из сигналов. nptl тот же механизм для вызова пользовательских handler'ов использует. Про java я не писал и в ответе не ожидал увидеть (и это не неожиданность из разряда приятных). Давайте без предрассудков. Вы можете линковать и не только свой код, так что вопрос с промежуточными фреймами интересен.

anonymous
()
Ответ на: комментарий от anonymous

копирование памяти процесса ядром от имени процесса.

Вы что-то сильно заблуждаетесь насчет доступа к памяти процесса.

Там функция для копирования памяти есть, которая возвращает ошибку, и эту ошибку надо проверять ручками. А код обработчика #PF знает про адреса, откуда можно легально получить исключение, потому что адрес обработчика заносится в специальную секцию макросом(http://lxr.free-electrons.com/source/arch/x86/include/asm/asm.h#L67). Eсли пошло исключение, то поток просто приостановится пока страница не будет доступна, после чего вернет управление по iret, после чего функция копирования возобновится с зафейленой команды и ничего не заметит. отакота. Ничего общего с исключениями этот механизм не имеет.

в С++ код отмотки исключений лежит вообще отдельно, и проблемы с ним были только при размотке стека(там поиск по таблицам изза каких-то патентных проблем).

i36_zubov
()
Ответ на: комментарий от i36_zubov

Сюрпризы, если между throw и catch есть frame от кода, скомпилированного без поддержки исключений

Для них это «сюрпризы», хотя этот код (на Си или ObjC например) им не марсиане подсунули, и вызов из него и в него происходит не потому сигналу датчика случайных чисел, а потому что кто-то так написал код. Не думая, как он привык это делать в жабе.

Специально для джунов: такие ситуации возможны в чисто плюсовом коде скомпиленом с форсированием исключений. Разработчики gcc не считают целесообразным это фиксить. По слухам разработчики других компилеров разделяют мнение разработчиков gcc.

cvv ★★★★★
()
Последнее исправление: cvv (всего исправлений: 1)
Ответ на: комментарий от i36_zubov

Я не заблуждаюсь.

Код memcpy/copy from user/ copy to user помечен, как вы и говорите, и это аналогия с использованием отладочной информации при обработке исключений. Стек разматывать не нужно, но я же и не говорил что нужно. А хотя - только адрес где exception возник интересен, так что можете считать что page fault handler его разматывает, аж на глубину 1, а более ему не интересно, такой контракт.

Может я просто слово аналогия в более общем смысле использую (а также слово exception)? Вы же сами предлагали не обсуждать error Path а обсудить выигрыш в эффективности обработки exception free ситуаций. Вот я пример вам привёл. И пояснил свою точку зрения - достижение такого эффекта в общем случае проблематично, а в user коде ещё и криво. А каков механизм нелокальность перехода и если там размотка и какая - не важно. Сфокусируйте сегодня на том что сами назвали важным. Покажите в каких ещё сценариях можно достичь эффективность используя «бесплатные» языковые exceptions. Только продумайте сценарий полностью.

А что ядро переехало на сайт с таким веселым именем?

anonymous
()
Ответ на: комментарий от cvv

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

i36_zubov
()
Ответ на: комментарий от i36_zubov

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

Я более склоняюсь к религии.

или может сам сишный код находит С++ный и начинает его вызывать потому что так ему захотелось?

1. Нестарые gcc научились пробрасывать исключения сквозь Сишный код (скомпиленный соответсвующим образом)

2. Тем не менее это не препятствует им «забывать» генерить проброс для чисто плюсовых фреймов даже при форсировании опциями коммандной строки.

cvv ★★★★★
()
Последнее исправление: cvv (всего исправлений: 1)
Ответ на: комментарий от i36_zubov

А какая разница на чем написан. Подход аналогичен. Выполняем копирование «без проверок». Нет дополнительных ветвлений/ усилий на проверку ошибок в случае если все хорошо. А в случае если не все хорошо - разбираемся с этим при помощи try/catch подобного механизма.

anonymous
()
Ответ на: комментарий от RazrFalcon

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

чтоб вызвать исключение его надо определить,исключение(тип) определяется с зависимостью в 10+ классов

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

так си отличается от джавы тем что он не тормозит

и если ты хочешь из си делать джаву-вот и будет тебе загрузка пары десятков MБ в память,а это жрет кэш процессора и тормозит как только может

когда код возврата идет напрямую в ардес и не жрет никаких классов,потребляя пару КБ процессорной памяти

anonymous
()
Ответ на: комментарий от anonymous

И как количество классов влияет на потребление памяти?

sizeof(std::exception) == 8

уже тут тыщу раз сказали

Такие же неадекваты, как местные сишники?

RazrFalcon ★★★★★
()
Ответ на: комментарий от cvv

им «забывать» генерить проброс для чисто плюсовых фреймов

вы мне хотите сказать, что в С++ исключения не работают или что?

i36_zubov
()

я смотрю, тут у народа какие-то дикие представления о том, как устроены исключения в С++.

поэтому дарю всем мануал о том, как это работает:

https://habrahabr.ru/post/279111/

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

i36_zubov
()
Ответ на: комментарий от cvv

я правда не понимаю, в чем состоит неработоспособность исключений в С++. если всё таки пройти по ссылке и прочитать как работают исключения, то можно узнать, что отмотку стека делает libstdc++, которая пользуется информацией из секции .eh_frame.

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

никаких «элементов везения» тут нет. если код вызывается из модулей, написаных на других языках, надо в функциях, которые торчат наружу(а они имеют сишный интерфейс) написать try {...} catch(...) {...}. Руками, да. Высунули их наружу - извольте предусмотреть. Если же люди хотят кодать не приходя в сознание, с ними не надо спорить. Им там удобнее

i36_zubov
()
Ответ на: комментарий от kawaii_neko

// no return statement

Хочется уточнить: тебя смущает отсутствие return в main? Так это разрешено стандартом.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

Т.е. при правильном использовании optional вторая проверка не нужна. Если бы ее добавили в реализацию optional, то это были бы лишние накладные расходы.

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

Или какой-то аналог ПМ на лябмдах:

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

Собственно, в расте именно в этом моменте всё здорово: есть паттерн матчинг (в том числе, в виде if let), есть подстраховка от неправильного использования (unwrap) и, если очень надо, можно использовать unsafe чтобы избежать проверок.

Если говорить о плюсах, то меня optional более-менее устраивает, но могли бы хотя бы асерт гарантировать, но если мне не изменяет память, стандарт хоть и описывает соответствующий макрос, но нигде его использование не требует.

DarkEld3r ★★★★★
()
Ответ на: комментарий от eao197

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

Умник, ты сам-то вкурил об чём этот код, скопипастеный тобой из cppreferrence.com?

alexku
()
Ответ на: комментарий от eao197

Газифицируешь тут ты. Раз мозгов не хватает, объясняю: ткни мышечкой в слово «комментарий» в заголовке сообщения, и будет тебе код, который ты высрал.

alexku
()
Ответ на: комментарий от eao197

И пробуем переписать все это без исключений, на кодах возврата. Насколько будет проще/лаконичнее/удобнее/надежнее?

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

fs::create_dir_all("sandbox/a/b")?;
File::create("sandbox/file1.txt")?;
File::create("sandbox/file2.txt")?;
for entry in fs::read_dir("sandbox")? {
    println!("{}", entry?.path().to_string_lossy());
}
fs::remove_dir_all("sandbox")

play.rust-lang.org

numas13
()
Ответ на: комментарий от numas13

Прекрасно. Но это код на Rust-е, а не на C++. Кроме того, Rust-оделы так же не смогли все сделать исключительно на возвращаемых значениях. И добавили panic-и.

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

eao197 ★★★★★
()
Ответ на: комментарий от alexku

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Прекрасно. Но это код на Rust-е, а не на C++.

В С++ достаточно инструментов, чтобы сделать так же, при желании.

Кроме того, Rust-оделы так же не смогли все сделать исключительно на возвращаемых значениях. И добавили panic-и.

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

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

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

numas13
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.