Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, kov_serg, Вы писали:
_>>На чистом С несколько иной подход. Тут более важна организация кода, а не конкретные локальные приёмы.
EP>RAII и исключения это глобальные приёмы. Про "иной подход" на C можно вот тут почитать.
Именно приёмы. И эти приёмы не решают реальные задачи. Скажу страшную вещь исключения "нинужны"
_>>Совершенно не обязательно тащить универсальность из C++ иногда есть требования запрещающие использование динамической памяти и вполне себе компилируется и работает на ограниченных ресурсах с заранее выделенными массивами для обработки.
EP>C++ прекрасно работает в таких рамках.
Особенно если под целевую платформу нет компилятора C++? Отсутствие всех удобств C++ по началу связывает руки, но потом выясняется что без всего этого можно спокойно обойтись. Даже возможность объявлять переменную где угодно скорее засоряет код чем облегчаетнаписание, особенно если это название шаблона с параметрами глубоко заныкаными в наймспейсах.
Здравствуйте, kov_serg, Вы писали:
_>>>На чистом С несколько иной подход. Тут более важна организация кода, а не конкретные локальные приёмы. EP>>RAII и исключения это глобальные приёмы. Про "иной подход" на C можно вот тут почитать. _>Именно приёмы.
Приёмы влияющие на код глобально
_>И эти приёмы не решают реальные задачи.
Смотри страдания по приведённой ссылке
_>>>Совершенно не обязательно тащить универсальность из C++ иногда есть требования запрещающие использование динамической памяти и вполне себе компилируется и работает на ограниченных ресурсах с заранее выделенными массивами для обработки. EP>>C++ прекрасно работает в таких рамках. _>Особенно если под целевую платформу нет компилятора C++?
Отсутствие компилятора это один из немногих случаев где оправданно использование C, по очевидным причинам.
_>Отсутствие всех удобств C++ по началу связывает руки, но потом выясняется что без всего этого можно спокойно обойтись.
Обойтись-то естественно можно, но более дорогой ценной.
U>Лёня Поттер с тобой не согласен. Он предпочитает GCC cleanup attribute, а это почти что RAII.
Они там клепать могут что угодно, это опенсорс, это делается just for fun. Вот тольно Линус следит, чтоб все эти новомодные фишки линуксовое ядро никак не затрагивали и материт если затрагивают, ибо они все толко проблемы создают и вредят. Простой пример когда эта RAII превращается в банальный ненужный гемор привёл выше.
Здравствуйте, b0r3d0m, Вы писали:
B>Я пишу код вместе с людьми, а людям, как известно, свойственно ошибаться. Вы хотите сказать, что ни разу не встречали ни одной ошибки, связанной с пропущенной деинициализацией?
Существуют простые и очевидные меры по организации кода и рабочего процесса, позволяющие не допускать ошибок, связанных с утечками ресурсов. Если кто-то не использует эти меры, он ССЗБ.
Здравствуйте, push, Вы писали:
P>Здравствуйте, smeeld, Вы писали:
S>>Обработка исключений в С++ слишком дорогая операция, P>По сравнению с чем? По сравнению с полным отказом от обработки ошибок — то да, дорогая. А если сравнивать с ручной обработкой ошибок — так можно сказать ничего не стоит.
int ErrorCodeFunction()
{
return 1;
}
void ThrowFunction() {
throw 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwTimeStart, dwTimeEnd;
int count = 1000000;
dwTimeStart = GetTickCount();
for (int i = 0; i < count; i++)
{
int result = ErrorCodeFunction();
}
dwTimeEnd = GetTickCount();
printf("Error code time = %d\n", dwTimeEnd - dwTimeStart);
dwTimeStart = GetTickCount();
for (int i = 0; i < count; i++)
{
try {
ThrowFunction();
}
catch(...) {}
}
dwTimeEnd = GetTickCount();
printf("Try catch time = %d\n", dwTimeEnd - dwTimeStart);
return 0;
}
Error code time = 15
Try catch time = 9000
Компилировать в Debug. Для Release Error code time будет просто 0, так как компилятор выкинет весь этот код с вызовом функции. Выкинуть код с исключением он не может.
Так что не надо заявлять, что обработка исключений столь уже дешева. Это все же вызов ядра (throw в VC++ вызывает RaiseException)
Здравствуйте, pestis, Вы писали:
P>Существуют простые и очевидные меры по организации кода и рабочего процесса, позволяющие не допускать ошибок, связанных с утечками ресурсов.
Можно вот с этого места поподробнее?
Сейчас как раз занимаюсь рефакторингом старого кода на C.
Есть объекты (структуры), которые хранят информацию, связанную с операциями ввода-вывода
на соответствующих объектах системы — файлы, папки, устройства и т.д. Объекты массово
создаются, живут, изменяются и уничтожаются, все это параллельно, в разных потоках.
Время жизни управляется подсчетом ссылок.
Проблема: работа с этими объектами "размазана" по всему коду, всего около трехсот
таких мест, некоторые фрагменты кода достаточно длинные и запутанные.
Вопрос: как обеспечить гарантию того, что тот, кто после меня будет работать с
этим кодом, не забудет вставить release_object в нужное место и освободить ссылку?
На C++ я бы взял shared_ptr и большая часть проблемы была бы решена автоматически.
O>Сейчас как раз занимаюсь рефакторингом старого кода на C.
Зачем вам это понадобилось? Почему код именно на C? Какие у вас есть возможности, полномочия, ресурсы?
O>Есть объекты (структуры), которые хранят информацию, связанную с операциями ввода-вывода O>на соответствующих объектах системы — файлы, папки, устройства и т.д. Объекты массово O>создаются, живут, изменяются и уничтожаются, все это параллельно, в разных потоках. O>Время жизни управляется подсчетом ссылок.
Не нужно создавать и удалять ресурсы в разных потоках. Вообще нежелательно использовать несколько потоков в С. Для этого есть более подходящие языки, которые, при необходимости прекрасно интегрируются с C кодом.
O>Проблема: работа с этими объектами "размазана" по всему коду, всего около трехсот O>таких мест, некоторые фрагменты кода достаточно длинные и запутанные.
Общее правило, места где выделяются ресурсы и места где освобождаются ресурсы должны быть локализованы и размечены комментариями. Код, использующий ресурсы может быть сколь угодно сложные, но он не должен ни выделять, ни освобождать ресурсы. Т.е. выделили ресурсы, посчитали, освободили ресурсы.
O>Вопрос: как обеспечить гарантию того, что тот, кто после меня будет работать с O>этим кодом, не забудет вставить release_object в нужное место и освободить ссылку?
Выделение и освобождение ресурсов должно быть рядом и размечено комментариями "тут выделил, там освободи". Любой код выделяющий ресурс, должен проходить peer review на предмет освобождения ресурса.
O>На C++ я бы взял shared_ptr и большая часть проблемы была бы решена автоматически.
На Python у вас была бы автоматическая сборка мусора и try/catch/finally блоки. А на Clojure у вас был бы software transactional memory.
Здравствуйте, okman, Вы писали:
O>Здравствуйте, pestis, Вы писали:
P>>Существуют простые и очевидные меры по организации кода и рабочего процесса, позволяющие не допускать ошибок, связанных с утечками ресурсов.
O>Можно вот с этого места поподробнее?
O>Сейчас как раз занимаюсь рефакторингом старого кода на C.
O>Есть объекты (структуры), которые хранят информацию, связанную с операциями ввода-вывода O>на соответствующих объектах системы — файлы, папки, устройства и т.д. Объекты массово O>создаются, живут, изменяются и уничтожаются, все это параллельно, в разных потоках. O>Время жизни управляется подсчетом ссылок.
O>Проблема: работа с этими объектами "размазана" по всему коду, всего около трехсот O>таких мест, некоторые фрагменты кода достаточно длинные и запутанные.
Код должен быть простой и логичный. Тогда вероятность забыть release будет намного меньше.
O>Вопрос: как обеспечить гарантию того, что тот, кто после меня будет работать с O>этим кодом, не забудет вставить release_object в нужное место и освободить ссылку? O>На C++ я бы взял shared_ptr и большая часть проблемы была бы решена автоматически.
shared_ptr неудобен и все равно нужно в некоторых местах использовать weak_ptr из-за кольцевых ссылок, что только усложняет дело.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, uncommon, Вы писали:
U>Здравствуйте, smeeld, Вы писали:
S>>RAII тоже сомнительная фича.
U>Лёня Поттер с тобой не согласен. Он предпочитает GCC cleanup attribute, а это почти что RAII.
U>И вообще в GCC вагон и маленькая тележка всяких расширений, без которых жизнь в C не мила.
Вот это мегакостыль, оставаться на сишке ради того, чтобы расширениями переизобрести в ней С++, долбанулись что ли.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Здравствуйте, okman, Вы писали:
O>Здравствуйте, pestis, Вы писали:
P>>Существуют простые и очевидные меры по организации кода и рабочего процесса, позволяющие не допускать ошибок, связанных с утечками ресурсов.
O>Можно вот с этого места поподробнее?
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Компилировать в Debug. Для Release Error code time будет просто 0, так как компилятор выкинет весь этот код с вызовом функции. Выкинуть код с исключением он не может.
PD>Так что не надо заявлять, что обработка исключений столь уже дешева. Это все же вызов ядра (throw в VC++ вызывает RaiseException)
Давай правильно сравнивать.
Кинуть исключение дороже чем вернуть значение, с этим никто не спорит.
Однако, исключения это исключительная ситуация, а посему это должно быть редким явлением.
А теперь сравним именно такой код:
#include <windows.h>
int ErrorCodeFunction(int count)
{
if (count < -1) return 0;
return count > 100 ? 1 : 2;
}
int ThrowFunction(int count) {
if (count < -1) throw 1;
return count > 100 ? 1 : 2;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwTimeStart, dwTimeEnd;
int count = 1000000;
dwTimeStart = GetTickCount();
for (int i = 0; i < count; i++)
{
int result = ErrorCodeFunction(count);
}
dwTimeEnd = GetTickCount();
printf("Error code time = %d\n", dwTimeEnd - dwTimeStart);
dwTimeStart = GetTickCount();
for (int i = 0; i < count; i++)
{
try {
int result = ThrowFunction(count);
}
catch (...) {}
}
dwTimeEnd = GetTickCount();
printf("Try catch time = %d\n", dwTimeEnd - dwTimeStart);
return 0;
}
Release:
Error code time = 0
Try catch time = 0
Увеличим количество итераций: int count = 100*1000*1000;
Release:
Error code time = 47
Try catch time = 62
Получаем разницу 15ms/10*1000*1000 = 1.5ns.
Не так много учитывая упрощение кода.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
Pzz>>Ну да, так и пишем, все руками. Полезность C++'ных автоматизмов сильно переоценена. В том смысле, что необходимость делать все явно добавляет, конечно, работы, но не так, чтобы очень много.
EP>Error-prone, легко забыть и трудно поддерживать.
Все эти C++'ные автоматизмы тоже error-prone. Особенно если учесть, как хорошо и неочевидно они могут быть запрятаны.
Pzz>>Обработка ошибок в виде явно возвращаемых кодов возврата — это не такой уж и плохой вариант. В отличии от исключений, все перед глазами и все легко читается, ошибка не прилетит неизвестно откуда и не улетит неизвестно куда.
EP>Error codes точно также летят наверх неизвестно куда. EP>Хотя часто вообще не летят, ибо на них забивают — пример
Там явно последовательность вызовов, которые вряд ли могут вернуть ошибку. Поэтому ребята притомились и перестали проверять
Для такого случая, как в том примере, есть еще один неплохой вариант. Пусть этот sAIMenu запоминает внутри себя, что очередное издевательство над ним вернуло ошибку, и игнорирует все последущие. Тогда можно смело совершенно написать 100500 действий в столбик, и проверить, нет ли там запомненной ошибки, в самом конце.
А еще можно, чем добавлять item'ы в меню сотней последовательных вызовов, сделать из них табличку и пройтись по ним циклом. Тогда ошибку придется проверять в одном месте внутри цикла, а не при каждом вызове, код станет короче, проще и более поддерживаемый.
В общем, все можно сделать аккуратно независимо от языка.
Pzz>>setjmp/longjmp для обработки ошибок очень мало подходят, это экзотические функции, которые бывают полезны в редких случаях.
EP>Их используют для эмуляции исключений.
Здравствуйте, smeeld, Вы писали:
S>Вот у Вас объект, он должен содержать установленное соединение с удалённым хостом, у нас RAII, поэтому соединение должно устанавливаться в конструкторе (типа никаких init/create). Более того, мы, как крутые C++-ники, обязательно сделаем emplace этого объекта в какой-нибудь контейнер, так как экземпляров этих объектов в программе создаётся много и чтоб руками их не уничтожать, нужно чтоб они уничтожались автоматически, которые не были унитожены в процессе исполнения. Так же мы не должны откатываться куда-то далеко, так как ошибка в установлении одного соединения, не значит, что будет ошибка в установлении другого соединения, при создании другого экземпляра. Что это значит?
Это значит, что проектировать нужно правильно. А не бросаться с шашкой наголо на танк. Устанавливать соединение в конструкторе? Ну можно, наверное.
Хинт — установление соединения всегда длительная и обычно весьма асинхронная операция.
S>А то, что при использовании каждого экземпляра, нам нужно будет постоянно проверять, установилось ли там соединение в конструкторе при создании экземпляра, или нет.
Второй хинт — при использовании любого "соединения" всегда нужно быть готовым к тому, что соединение, которое еще такт назад было "хорошим", вдруг стало "плохим". Более того, нужно быть готовым к тому, что соединение испортилось в процессе использования.
И тут внезапно удобство исключений становится как бы весьма очевидным.
_NN>Получаем разницу 15ms/10*1000*1000 = 1.5ns. _NN>Не так много учитывая упрощение кода.
Ну наконец-то, хоть кто-то вменяемый.
Исключения нужно юзать осторожно, и к их понимаю когда и почему и для чего их следует юзать, приходишь ой как не скоро. Это уже про архитектуру системы, а к таким пониманиям долго идти. А вы тут школота, школота, RAII они в конструктор пихнутЪ — об чем вы?
Школоте дали веревку достаточной длины, вот они и палят во все стороны. А "веревка" тут кстати не виновата.
В исключениях ключевое слово к примеру "библиотека". Кто в теме, тот меня понял
_NN>>Получаем разницу 15ms/10*1000*1000 = 1.5ns. _NN>>Не так много учитывая упрощение кода. C>Ну наконец-то, хоть кто-то вменяемый.
C>Исключения нужно юзать осторожно, и к их понимаю когда и почему и для чего их следует юзать, приходишь ой как не скоро. Это уже про архитектуру системы, а к таким пониманиям долго идти. А вы тут школота, школота, RAII они в конструктор пихнутЪ — об чем вы :??? :? Школоте дали веревку достаточной длины, вот они и палят во все стороны. А "веревка" тут кстати не виновата.
Поэтому как раз исключение в конструкторе то что нужно.
1. Исключение бросается только в случае исключительной ситуации когда создать объект не имеет смысла.
Пример:
class A
{
A(int count)
{
// Меньше 10 в коде не будет, а если кто-то опечатался пускай получает.if(count<10) throw exception("ERROR");
}
}
2. Если это не исключительная ситуация, а часть работы программы, то не должно быть исключения.
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Компилировать в Debug. Для Release Error code time будет просто 0, так как компилятор выкинет весь этот код с вызовом функции. Выкинуть код с исключением он не может.
PD>>Так что не надо заявлять, что обработка исключений столь уже дешева. Это все же вызов ядра (throw в VC++ вызывает RaiseException) _NN>Давай правильно сравнивать. _NN>Кинуть исключение дороже чем вернуть значение, с этим никто не спорит. _NN>Однако, исключения это исключительная ситуация, а посему это должно быть редким явлением.
_NN>А теперь сравним именно такой код:
_NN> ... _NN>Получаем разницу 15ms/10*1000*1000 = 1.5ns. _NN>Не так много учитывая упрощение кода.
Пример абсолютно бессмысленный т.к. count всегда >0.
Чистый C применяется только в ядре,где нужно работать с оборудованием и C++ только мешает, и в коде embedded устройств. Последние нередко real-time и задержки на обработку исключений составляют существенную проблему и недопустимы.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, _NN_, Вы писали:
_NN>2. Если это не исключительная ситуация, а часть работы программы, то не должно быть исключения.
Именно это я и имел ввиду! Что ситуация должна быть исключительной!!!
А этому просто не учили. Так, между делом, брякнут на лекции "исключения для исключительных ситуаций", ну понятно. А что такое исключительная ситуация никто и не объяснит, ибо сами не знают.
Я таки долго доходил своими мозгами когда "оно надо", пока не написал мутно-ядреный полубиблиотечный код с кучей маткриптографии внутри и в нагрузку кучи "дряни" аля "хак" (ибо этот код для защиты использовался).
Вот я пошел по пути кодов возврата, как тут разрабы_которые_не_такие, которые не ошибаются. Ох и наелся я ребята всякого. А главное юзера мои откушали полной ложкой, славу богу всего парочка, и огромное им спасибо за помощь и репорты.
Были б исключения сделал, проблем бы и не было в вовсе. А так…
Для сишников: не важно насколько красивый\быстрый\недорогой_в_ресурсах ваш код, если ваш код не работает.
PS: Для законченных сишников: "код не работает" это не означает Access Vioalation или просто тормоза. Это значит — вы не поверите — что он не делает того, что от не требуется, или делает, но не так, как ожидают от него.
lpd>Чистый C применяется только в ядре,где нужно работать с оборудованием и C++ только мешает, и в коде embedded устройств. Последние нередко real-time и задержки на обработку исключений составляют существенную проблему и недопустимы.
С этим я абсолютно согласен. Я просто против оголотелого "ДваПлюса плохи\прекрасны\тормозы" или "Чистый Си прекрасен\плох\гемороен" (нужное подчеркнуть).
Кинжал хорош для того, у кого он окажется в нужный момент, и плох для того, у кого не окажется (С) Белое Солнце пустыни
Все очень сильно зависит от задачи. А оголтелая критика, как и восхваление, это по моему признак небольшого опыта. "Мудрым способно сомневаться" (тоже (ц)).