Здравствуйте, lpd, Вы писали:
lpd>Пример абсолютно бессмысленный т.к. count всегда >0.
Как раз это и смысл исключения, защититься от случайной ошибки.
Без него придется писать:
Result ErrorCodeFunction(int count, int* returnValue)
{
if (count < -1) return ResultErrorInvalidArgument;
if (!returnValue return ResultErrorInvalidReturnArgument;
*returnValue = count > 100 ? 1 : 2;
return ResultSuccess;
}
А потом протаскиввать это всё через все функции и все ветвления.
В Rust и приделали макрос try! который только этим и занимается.
lpd>Чистый C применяется только в ядре,где нужно работать с оборудованием и C++ только мешает, и в коде embedded устройств. Последние нередко real-time и задержки на обработку исключений составляют существенную проблему и недопустимы.
Не нужно смешивать всё и сразу.
Во первых в ядре можно и С++ а можно и даже более высокоуровневые языки та же Singularity.
Проблемы с C++ там не от использования исключений, а например, от того, что таблица виртуальных функций может быть выгружена из памяти в неподходящий момент.
Разработка реального времени это другая тема и даже там возможен C++.
В С ,между прочим, тоже нужно понимать что стоит делать , а что не стоит делать в этих случаях. Только об этом почему-то не вспоминают.
Здравствуйте, smeeld, Вы писали:
S>Про откат на самые начала, при возникновении ошибки, с помощью исключений всем известно, выше говорилось про контроль результатов вызова функций, ошибки в которых не должны приводить к откату на самые начала. У Вас только два варианта, смотреть возврат из функции, или бросать в ней исключение и ловить их сразу за вызовом этой функции. Второй вариант на порядок более затратный по количеству выполняемых процессором операций для обработки ошибки исполнения функции, чем первый.
Зачем делать откат на самые начала? Зачем ловить исключение сразу после вызова функции? Это неправильное использование исключений, бессмысленное. Как я понимаю вы писали на С и пытаетесь применить свой опыт на С++.
Подход с исключениями заключается в том, чтобы писать обычный код без проверки вызова каждой функции. Проверка каждой функции в принципе не нужна, так как в подавляющем большинстве случаев ни корректно восстановиться, ни корректно залогировать проблему на месте возникновения невозможно (иначе в логах в основном будет низкоуровневый мусор, а не полезные сообщения). В принципе всегда интересует выполнился ли корректно блок кода в целом, а не его микрочасть.
Для обработки исключений выбираются самые информативные места в случае логгирования, либо места возможного восстановления.
Например, запись некоего события в базу. В целом интересует получилось ли записать его в базу или нет.
Для этого надо:
1) подготовить сообщение (отформатировать) — тут может быть ошибка форматирования
2) соединится с базой — ошибка соединения
3) сохранить сообщение — ошибка соединения + ошибка сохранения
4) показать пользователю сообщение, в случае неудачи
В случае кодов возврата есть 3 способа обработки ошибок:
1) показывать пользователю сообщение по месту возникновения ошибки == нарушение модульности (сильная связанность кода, невозможность повторного использования), избыточность, сложность поддержки и отладки. Так поступали в самом начале эры программирования.
2) возвращать ошибку вверх по стеку == трудоёмкость (для реального проекта невозможно сделать единственный универсальный код возврата, потому нужно будет в каждой функции приводить его в новому коду. И, как правило, это почти никогда невозможно сделать без потери информации), сильная связанность, избыточность, очень высокая сложность поддержки и отладки. Способ то особо и не прижился из-за количества создаваемых проблем.
3) завести универсальную глобальную переменную через которую передавать полную информацию, а "на месте" использовать простые коды возврата. Способ достаточно долго прожил. Проблема в том, что при коллективной разработке (количеством более пары человек) невозможно отревьювить всех. И из-за человеческого фактора возникают проблемы когда кто-то не проверил код, либо сбросил/изменил глобальную переменную ошибки раньше, чем она обработалась в нужном модуле. Начинаются игры по типу временного сохранения глобальной переменной ошибки + выполнение своего кода и восстановления глобальной ошибки. В случае багов всё это очень муторно отлаживать (сложность не большая, но трудоёмкость отладки огромная). Из-за сложности начинают появляться "глобальный код ошибки2", "глобальный код ошибки3" и т.д. Появляются документы, описывающие сценарии их использования. В итоге каждому программисту проще завести свой глобальный код ошибки. Хаос во всей красе.
В итоге, учитывая вышеописанные проблемы кодов возврата, был разработан механизм исключений для единственного универсального способа работы с ошибками. Теперь просто бросается конкретный тип исключения и в нужном месте он (и только он) обрабатывается. Дополнительно, если требуется, исключение можно обогатить информацией при раскрутке стека. Замечу, что всё это можно делать без увеличения сложности/связности, отсутствует избыточность.
S>Так же мы не должны откатываться куда-то далеко, так как ошибка в установлении одного соединения, не значит, что будет ошибка в установлении другого соединения, при создании другого экземпляра.
Цикл по созданию соединений с отловом ошибок? А в чём проблема то?
S>Что это значит? А то, что при использовании каждого экземпляра, нам нужно будет постоянно проверять, установилось ли там соединение в конструкторе при создании экземпляра, или нет. То есть, код везде будет содержать пресловутое if(is_good()) then_cool(), на что будут затрачиваться процессорные такты. На фоне этого init/create, вызываемое единожды при создании экземпляра такого объекта перед помещением его в контейнер, будет более эффективным решением.
Зачем? Объект либо создался (всё ок) либо нет (исключение). Никаких проверок в конструкторе не нужно. В этом и заключается удобство RAII.
Более того в вашем init/create обязан быть код, идентичный тому, что в конструкторе. Откуда взяться эффективности?
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, lpd, Вы писали:
lpd>>Пример абсолютно бессмысленный т.к. count всегда >0. _NN>Как раз это и смысл исключения, защититься от случайной ошибки. _NN>Без него придется писать:
_NN>...
Я вовсе не против C++, кроме последних стандартов.
_NN>Проблемы с C++ там не от использования исключений, а например, от того, что таблица виртуальных функций может быть выгружена из памяти в неподходящий момент.
Вообщем-то элементы ООП в ядре Linux присутствуют — аналог this для структур, реализованного макросом. Но проблем несколько. Прежде всего процессор для кода ОС является не абстракцией, а всплывают низкоуровневые моменты вроде той же памяти, переключения контекста и прерываний. Посмотрю, как будешь кидать exception из прерывания и кто его будет обрабатывать. Получится, что для существенной части кода будет запрет на исключения, либо смесь чистого C и C++ с переходниками. Все это многоплатформенно. И стоит ли игра свеч? Особенно если учесть, что stl полностью с std::thread не будет.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, landerhigh, Вы писали:
L>Хинт — установление соединения всегда длительная и обычно весьма асинхронная операция.
Да, длительная, асинхронная, о у ас же е должно быть никаких init/create, у нас RAII, поэтому всё в конструкторе.
L>Второй хинт — при использовании любого "соединения" всегда нужно быть готовым к тому, что соединение, которое еще такт назад было "хорошим", вдруг стало "плохим". Более того, нужно быть готовым к тому, что соединение испортилось в процессе использования. L>И тут внезапно удобство исключений становится как бы весьма очевидным.
Да, может стать плохим, но это уже будет видно по возвратам из операций чтения/записи в сокет, никакого is_good тут не нужно, а вот для того, чтоб проверить установилось ли соединение при создании экземпляра в его конструкторе, перед каждой операцией на сокете нужно будет делать is_good, иначе системный облом.
Здравствуйте, T4r4sB, Вы писали:
TB>Здравствуйте, uncommon, Вы писали:
U>>Здравствуйте, smeeld, Вы писали:
S>>>RAII тоже сомнительная фича.
U>>Лёня Поттер с тобой не согласен. Он предпочитает GCC cleanup attribute, а это почти что RAII.
U>>И вообще в GCC вагон и маленькая тележка всяких расширений, без которых жизнь в C не мила.
TB>Вот это мегакостыль, оставаться на сишке ради того, чтобы расширениями переизобрести в ней С++, долбанулись что ли.
Вы похоже ещё не видали C11 _Generic и C99 inline
Здравствуйте, lpd, Вы писали:
lpd>Здравствуйте, _NN_, Вы писали:
_NN>>Здравствуйте, lpd, Вы писали:
lpd>>>Пример абсолютно бессмысленный т.к. count всегда >0. _NN>>Как раз это и смысл исключения, защититься от случайной ошибки. _NN>>Без него придется писать:
_NN>>... lpd>Я вовсе не против C++, кроме последних стандартов.
_NN>>Проблемы с C++ там не от использования исключений, а например, от того, что таблица виртуальных функций может быть выгружена из памяти в неподходящий момент. lpd>Вообщем-то элементы ООП в ядре Linux присутствуют — аналог this для структур. Но проблем несколько. Прежде всего процессор для кода ОС является не абстракцией, а всплывают низкоуровневые моменты вроде той же памяти, переключения контекста и прерываний. Посмотрю, как будешь кидать exception из прерывания и кто его будет обрабатывать.
Понятно, что где-то нужно ограничить себя в фичах.
Напомню, что в C это аналогично. До инициализации C Runtime тоже не всё позволено делать.
lpd>Получится, что для существенной части кода будет запрет на исключения, либо смесь чистого C и C++ с переходниками. Все это многоплатформенно. И стоит ли игра свеч? Особенно если учесть, что stl полностью с std::thread не будет.
Как минимум наличии типа как ссылки (для защиты от нулевых указателей) и возможность писать шаблонный код (который вообще может вылиться в оптимизацию во время компиляции) уже могли бы исключить многие ошибки на этапе разработки.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Так что не надо заявлять, что обработка исключений столь уже дешева. Это все же вызов ядра (throw в VC++ вызывает RaiseException)
Вы либо пытаетесь ввести в заблуждение, либо не до конца понимаете оптимизацию в с++. Тут даже без компиляции очевидно, что код с ErrorCodeFunction вырезается (и это абсолютно никак не связано с кодами возврата). В реальной функции ничего не вырезается.
В реальной разработке всё кардинально иначе.
В случае кодов возврата вам требуется проверять код каждый раз и для каждой функции, и вне зависимости от успеха/неудачи.
В случае исключений механизм исключений включается только при неудаче и обрабатывается только при ней, и обрабатывается (в основном) только в одном месте.
Учитывая, что количество неудач по сравнению с количеством успешного выполнения функций ничтожно мало — то и накладные расходы на исключения фактически можно считать равными нулю. И нулю не только по сравнению с кодами возврата, а вообще в принципе. Потому что, при успехе накладных расходов почти нет, а при неудаче важность потребления процессорного времени по сравнению с важностью обработки ошибки стремиться к нулю.
Здравствуйте, smeeld, Вы писали:
S>Да, длительная, асинхронная, о у ас же е должно быть никаких init/create, у нас RAII, поэтому всё в конструкторе.
Вы неправильно используете RAII. Да, его можно использовать и таким образом — тех проблем на которые вы намекаете не будет. Но тут дело в другом: архитектура RAII подразумевает, что объект может себя инициализировать. В случае коннекшена, инициализация объекта не зависит от объекта, а зависит от внешних факторов. Потому либо вынесение соединения в метод connect (архитектурно правильное решение, можно увидеть во всех библиотеках), либо вы сами отвечаете за созданную архитектуру.
Здравствуйте, _NN_, Вы писали:
lpd>>Получится, что для существенной части кода будет запрет на исключения, либо смесь чистого C и C++ с переходниками. Все это многоплатформенно. И стоит ли игра свеч? Особенно если учесть, что stl полностью с std::thread не будет. _NN>Как минимум наличии типа как ссылки (для защиты от нулевых указателей) и возможность писать шаблонный код (который вообще может вылиться в оптимизацию во время компиляции) уже могли бы исключить многие ошибки на этапе разработки.
Для защиты от нулевых указателей первые страницы памяти по адресу 0 отображаются особенным образом и при обращении выдают исключение. Причем это исключение не вешает ядро, а записывается в лог.
В некоторых случаях в ядре могли бы быть полезными именно объекты/классы, которые сейчас реализованы несколько костыльно через макросы. Но вообще, объекты нужны для моделирования предметной области в коде, а ядро занимается не этим.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
P>Зачем делать откат на самые начала? Зачем ловить исключение сразу после вызова функции? Это неправильное использование исключений, бессмысленное. Как я понимаю вы писали на С и пытаетесь применить свой опыт на С++. P>Подход с исключениями заключается в том, чтобы писать обычный код без проверки вызова каждой функции. Проверка каждой функции в принципе не нужна, так как в подавляющем большинстве случаев ни корректно восстановиться, ни корректно залогировать проблему на месте возникновения невозможно (иначе в логах в основном будет низкоуровневый мусор, а не полезные сообщения). В принципе всегда интересует выполнился ли корректно блок кода в целом, а не его микрочасть. P>Для обработки исключений выбираются самые информативные места в случае логгирования, либо места возможного восстановления............
Мля, Вы не понимаете, всё то, что Вы говорите, похоже на теоретическую воду. Причин отказа от фишек С++ могут быть сколько угодно. Вот у нас функция, инициализирующая ряд ресурсов, чтоб не писать простыню, пишем ряд функций для инициализации каждого ресурса и вызываем их по порядку. В каждой инициализация может окончиться ошибкой, но в этом случае не нужно откатываться куда-то дальше этой функции, а просто передать ей другие параметры и вызвать снова. Как ловить ошибку в каждой такой функции, считывать код возврата или кидать в ней исключение? Первое будет производительней, особенно если эта инициализирующая ресурсы функция вызывается несколько десятков тысяч раз в секунду, это, например, сервер, обрабатывающий запросы. Если использовать исключения, будем десятки тысяч раз в секунду нырять в throw. Оно надо? Лучше вернуть код возрата, тем более если там возврат может быть только true или false. Так же и с инициализацией, объекты создаются и уничтожаются тысячами в секунду, инициализировать их с init/create-и смотреть на возврат-простое эффективное решение, чем пихать всё в конструктор, проверяться исключениями, постоянно делать is_good, объекту.
Короче, теоретически C++-ные вещи выглядят красиво, но на практике, особенно если речь идёт об исполнении проги в агрессивных условиях исполнения, эти вещи часто превращается просто в уродство, и нужно, как бегущему легкоатлету нужно болтающееся конское седло между ног. Ядро ОС исполняется в агрессивной среде, агрессия идёт со стороны железа, прикладных прог, приходящих извне данных, там С++-ные фишки будут жутко мешать, они только всё усложняют, потому С++ в ядра с намордником не пускают
Здравствуйте, lpd, Вы писали:
lpd>Чистый C применяется только в ядре,где нужно работать с оборудованием и C++ только мешает, и в коде embedded устройств. Последние нередко real-time и задержки на обработку исключений составляют существенную проблему и недопустимы.
Распространённое заблуждение. В своё время программировал POS-терминалы (те, что в супермаркетах на кассах стоят). Самый хардкорный embeded, работа с оборудованием (3-4 каторчки, lpt, GSM модуль, ethernet, аппаратный криптомодуль). После перехода с С на С++ — прямо как глоток свежего воздуха, разработка по ощущениям упростилась раз в 100. От исключений вообще все в восторге. После убирания всех проверок кодов ошибок и замены их на исключения получили небольшой прирост в производительности. В случае исключений ценность процессорного времени ничтожна по сравнению с важностью обработки ошибки (к тому же абсолютно никак не заметна пользователю даже на старых маломощных POS`ах).
Здравствуйте, lpd, Вы писали:
lpd>Здравствуйте, _NN_, Вы писали:
lpd>>>Получится, что для существенной части кода будет запрет на исключения, либо смесь чистого C и C++ с переходниками. Все это многоплатформенно. И стоит ли игра свеч? Особенно если учесть, что stl полностью с std::thread не будет. _NN>>Как минимум наличии типа как ссылки (для защиты от нулевых указателей) и возможность писать шаблонный код (который вообще может вылиться в оптимизацию во время компиляции) уже могли бы исключить многие ошибки на этапе разработки.
lpd>Для защиты от нулевых указателей первые страницы памяти по адресу 0 отображаются особенным образом и при обращении выдают исключение. Причем это исключение не вешает ядро, а записывается в лог.
В некоторых системах это не так
Кроме того указатель можно не инициализировать, а вот ссылку обязаны.
Только вчера нашёлся баг годовой давности, потому что кто-то забыл поставить NULL.
lpd>В некоторых случаях в ядре могли бы быть полезными именно объекты/классы, которые сейчас реализованы несколько костыльно через макросы. Но вообще, объекты нужны для моделирования предметной области в коде, а ядро занимается не этим.
Здравствуйте, push, Вы писали:
P>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Так что не надо заявлять, что обработка исключений столь уже дешева. Это все же вызов ядра (throw в VC++ вызывает RaiseException)
P>Вы либо пытаетесь ввести в заблуждение, либо не до конца понимаете оптимизацию в с++. Тут даже без компиляции очевидно, что код с ErrorCodeFunction вырезается (и это абсолютно никак не связано с кодами возврата). В реальной функции ничего не вырезается.
P>В реальной разработке всё кардинально иначе. P>В случае кодов возврата вам требуется проверять код каждый раз и для каждой функции, и вне зависимости от успеха/неудачи. P>В случае исключений механизм исключений включается только при неудаче и обрабатывается только при ней, и обрабатывается (в основном) только в одном месте. P>Учитывая, что количество неудач по сравнению с количеством успешного выполнения функций ничтожно мало — то и накладные расходы на исключения фактически можно считать равными нулю. И нулю не только по сравнению с кодами возврата, а вообще в принципе. Потому что, при успехе накладных расходов почти нет, а при неудаче важность потребления процессорного времени по сравнению с важностью обработки ошибки стремиться к нулю.
Ты представляешь сколько тактов занимает сравнение кода возврата в регистре с кодом успеха?
Для прикладного кода да, исключения подходят. Но иногда(например, real-time код) необходимо понимать их ограничения по скорости.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, _NN_, Вы писали:
_NN>Поэтому как раз исключение в конструкторе то что нужно.
У Вас объектов создаётся десятки тысяч в секунду, и как минимум в половине случаев результат создания будет отрицательным, например, у Вас объект-соединение, соединение к хосту, находящемуся за Symmetric Cone NAT (p2p у нас), внешний адрес которого постоянно меняется. Это у Вас в секунду будет генериться десятки тысяч исключений, десятки тысяч раз будете нырять в throw, это будет нафиг не нужная мощная нагрузка. И мерзкая init/create покажется единственно правильным решением.
Здравствуйте, pestis, Вы писали:
O>>Сейчас как раз занимаюсь рефакторингом старого кода на C.
P>Зачем вам это понадобилось? Почему код именно на C? Какие у вас есть возможности, полномочия, ресурсы?
1. Работа такая.
2. Какое это имеет отношение к обсуждаемому вопросу?
3. Какое это имеет отношение к обсуждаемому вопросу?
P>Не нужно создавать и удалять ресурсы в разных потоках. Вообще нежелательно использовать несколько потоков в С. Для этого есть более подходящие языки, которые, при необходимости прекрасно интегрируются с C кодом.
У меня набор callback-функций, предоставляемых операционной системой, которые
срабатывают при наступлении определенных событий. Открыли устройство — сработал один callback.
Закрыли — сработал другой. И т.д. В этих callback-функциях происходит создание различных
ресурсов, работа с ними и удаление. Управлять тем, чтобы ресурсы создавались где-то в одном
месте и в одном потоке, я не могу.
P>Выделение и освобождение ресурсов должно быть рядом...
В моем случае это неприменимо.
P>Любой код выделяющий ресурс, должен проходить peer review на предмет освобождения ресурса.
Review не дает гарантию, что все ссылки во всех ветках выполнения были освобождены.
P>На Python у вас была бы автоматическая сборка мусора и try/catch/finally блоки. А на Clojure у вас был бы software transactional memory.
Мечтаю увидеть, как пишутся драйверы и прочий системный низкоуровневый код на Python/Clojure/etc.
Здравствуйте, push, Вы писали:
P>Здравствуйте, lpd, Вы писали:
lpd>>Чистый C применяется только в ядре,где нужно работать с оборудованием и C++ только мешает, и в коде embedded устройств. Последние нередко real-time и задержки на обработку исключений составляют существенную проблему и недопустимы.
P>Распространённое заблуждение. В своё время программировал POS-терминалы (те, что в супермаркетах на кассах стоят). Самый хардкорный embeded, работа с оборудованием (3-4 каторчки, lpt, GSM модуль, ethernet, аппаратный криптомодуль). После перехода с С на С++ — прямо как глоток свежего воздуха, разработка по ощущениям упростилась раз в 100. От исключений вообще все в восторге. После убирания всех проверок кодов ошибок и замены их на исключения получили небольшой прирост в производительности. В случае исключений ценность процессорного времени ничтожна по сравнению с важностью обработки ошибки (к тому же абсолютно никак не заметна пользователю даже на старых маломощных POS`ах).
Терминалы в кассах никак не real-time, поэтому там и не было проблем со скоростью. Если C++ тебе так нравится, перепиши на нем ядро Linux .
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, smeeld, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
_NN>>Поэтому как раз исключение в конструкторе то что нужно.
S>У Вас объектов создаётся десятки тысяч в секунду, и как минимум в половине случаев результат создания будет отрицательным, например, у Вас объект-соединение, соединение к хосту, находящемуся за Symmetric Cone NAT (p2p у нас), внешний адрес которого постоянно меняется. Это у Вас в секунду будет генериться десятки тысяч исключений, десятки тысяч раз будете нырять в throw, это будет нафиг не нужная мощная нагрузка. И мерзкая init/create покажется единственно правильным решением.
Если это сценарий то в этом случае конечно не нужны исключения и просто нужно вернуть успешное значение или неудачу.
// Так
optional<Connection> connectHost(...)
{
if(can cannot) return optional<Connection>(successful values);
else return optional<Connection>(); // no value
}
// Или например так
variant<Connection, ConnectionException> connectHost(...) { ... }
Здравствуйте, lpd, Вы писали:
lpd>shared_ptr неудобен и все равно нужно в некоторых местах использовать weak_ptr из-за кольцевых ссылок, что только усложняет дело.
Shared_ptr в любом случае лучше, чем управление ссылками "вручную".
А кольцевые ссылки — это проблема совсем другого уровня, чем утечка ссылок.
_NN>Если это сценарий то в этом случае конечно не нужны исключения и просто нужно вернуть успешное значение или неудачу.
Вы не поняли, тут адепты C++ утверждают, что любое решение обязательно должно быть в рамках RAII и использовать исключения, иначе это говнокод. Тут пытаемся доказать, что есть куча ситуаций, когда вещи из С++ в рог не впёрлись, но они продолжают настаивать, что абсолютно для любой ситуации и любой задачи, использование вещей из C++ есть заведомо более производительное и правильное решение.
Здравствуйте, smeeld, Вы писали:
S>У Вас объектов создаётся десятки тысяч в секунду, и как минимум в половине случаев результат создания будет отрицательным, например, у Вас объект-соединение, соединение к хосту, находящемуся за Symmetric Cone NAT (p2p у нас), внешний адрес которого постоянно меняется. Это у Вас в секунду будет генериться десятки тысяч исключений, десятки тысяч раз будете нырять в throw, это будет нафиг не нужная мощная нагрузка. И мерзкая init/create покажется единственно правильным решением.
Это, я полагаю, теоретическое построение, да?
Symmetric cone NAT вряд ли выдержит десятки тысяч попыток установления соединения в секунду. Просто таблицы переполнятся, и дальше будет плохо. Собственно, обычные домашние роутеры зачастую не выдерживают и нескольких десятков попыток в минуту, просто зависают к чертовой матери.