[C++, gcc] не вызываются деструкторы при вызове исключения

erotic

Простейший пример:

#include <iostream>
#include <stdexcept>

using namespace std;

class A
{
public:
~A { cout << "~A" << endl; }
};

int main
{
auto_ptr<A> a(new A;
throw logic_error("Logic error");
return 0;
}

При запуске выдает следующее:
terminate called after throwing an instance of 'std::logic_error'
what: Logic error
Аварийный останов

Почему?

kokoc88

Почему?
Такое поведение при unhandled software exception.

freezer

потому что catch'а нет

erotic

потому что catch'а нет
Бред.
На момент вызова исключения переменная a является полностью сконструированной локальной переменной, деструктор которой обязан быть вызван при выходе из блока независимо от того, произошло ли оно нормально или по исключению.
P.S. Наверное стоит пояснить, что мой вопрос заключается в том, почему не вызывается деструктор a.

erotic

Такое поведение при unhandled software exception.
Имеется ввиду то же самое, что написал Atilla: "потому что catch'а нет"? Тогда бред.

ppplva

Не бред, а стандарт.
С++'03, 15.3.9

ppplva

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

erotic

Хорошо. Предположим, дело в том, что т.к. компилятор видит, что исключение вызывается именно в функции main, и если в ней обработчика нет, то может быть он сразу и прописывает unhandled без вызова деструкторов.
Сделаем тогда вот что: перенесем создание объекта и вызов исключения в функцию. Я оставлю ее в этом же файле, но без ограничения общности предположим, что эта функция находится в какой-нибудь библиотеке, которая понятия не имеет, встретится ли ей обработчик исключения или нет.
Ок, я немного модифицирую пример:
 #include <iostream>
#include <stdexcept>

using namespace std;

class A
{
public:
~A { cout << "~A" << endl; }
};

void f
{
auto_ptr<A> a(new A;
throw logic_error("Logic error");
}

int main
{
f;
return 0;
}

Результат тот же самый. Деструктор не вызывается. Единственное, что приходит в голову на такое (ну, кроме ошибки в gcc или какой-то неправильной его настройки) - это то, что при раскручивании стека ни один деструктор не вызывается, пока мы не встретим обработчик, и тогда они вызовутся все скопом (по очереди, конечно или пока мы не дойдем до конца программы и не обнаружим, что обработчика нет, и тогда забьем на деструкторы вообще. Но эта схема кажется мне неправдоподобной, и, более того, лишенной смысла - выходит, что если я хочу, чтобы программа при вызове любого исключения просто завершалась, но освобождала все ресурсы, я обязан все содержимое main совать в try-блок и делать в конце catch?

erotic

Блин, похоже, так и есть, 15.5.1:
Хорошо. Предположим, дело в том, что т.к. компилятор видит, что исключение вызывается именно в функции main, и если в ней обработчика нет, то может быть он сразу и прописывает unhandled без вызова деструкторов.

Я вот только что резко разочаровался в C++ :(

freezer

Бли-и-ин. Ну тебе же уже объяснили, что это - стандартное поведение, и оно должно быть реализовано всеми компиляторами и рантаймами. Деструктор нужен чтобы освобождать программные ресурсы, когда идёт аварийное завершение программы (terminate ресурсы сами освободятся по случаю смерти процесса.

erotic

Деструктор нужен чтобы освобождать программные ресурсы, когда идёт аварийное завершение программы (terminate ресурсы сами освободятся по случаю смерти процесса.
Не понял глубокой мысли. Где-то пропущена запятая или точка?
Да, система освободит все ресурсы. Все, да не все. Мне надо, чтобы в любом случае в конце программы вызывалась некоторая очистка. Например, подключенная камера при аварийном завершении программы не сбрасывает своего состояния, в результате после этого ей невозможно пользоваться без дополнительных усилий.
Выхода два - устанавливать очистку камеры на какую-либо из стандартных функций, вызываемых при выходе (вроде есть такие но тогда надо, чтобы эта очистка производилась только один раз, а еще она заложена глубоко в недрах библиотеки, т.ч. все равно проблематично сделаь.
Второй выход - это заключать весь main в try-блок, т.к. исключение может возникнуть в любой момент. Но это нежелательно, т.к. в принципе может приводить к накладным расходам.
Вот и получается, что говно-поведение в стандарте :(

freezer

terminate - это само по себе аварийное завершение. Если у тебя такое происходит - значит ты что-то делаешь не так, есть повод задуматься. ;)
> Вот и получается, что говно-поведение в стандарте :(
а почему не в действиях программиста?

erotic

а почему не в действиях программиста?
Ну да, ты прав, конечно.

Dasar

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

using namespace std;

class A
{
public:
~A { cout << "~A" << endl; }
};

void f
{
auto_ptr<A> a(new A;
int x = 1/(int)sin(0);
}

int main
{
f;
return 0;
}


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

okunek

А в каком месте здесь будет кинуто исключение?

freezer

деление на букву "О"

kokoc88

в функции f может не формироваться блок вызова деструктора, т.к. компилятор считает, что исключению здесь не откуда взяться.
Здесь сформируется. И вообще integer division by zero считается SEH, на них не распространяются стандарты C++. Деструкторы точно не вызываются в MSVC, если объявить функцию throw но всё же выкинуть исключение.
По теме можно предложить перевыкидывать исключения через catch(...) { throw; } Это иногда позволяет сделать удобное решение какой-то проблемы.

SPARTAK3959

А в чем проблема все в main окружить в try/catch? У меня практически во всех моих программах на java main окружен в try/catch.

erotic

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

kokoc88

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

Missi4ka

cout << "~A" << endl;

Попробуйте использовать cerr вместо cout.

tamusyav

А чем он лучше-то? Что один - поток, что другой - поток. Назначение лишь разное, а в остальном - разницы никакой.

slonishka

боюсь предположить, что будет если запустят с 2>&1. :o :o :o

tokuchu

А чем он лучше-то? Что один - поток, что другой - поток. Назначение лишь разное, а в остальном - разницы никакой.
Стандартные функции вывода (printf, <<) ведут себя по-разному в случае stderr и stdout. 1-й сразу идёт на вывод обычно, а 2-й буферизуется. При аварийном завершении программы буфер никто "довыводить" не будет.

sbs-66

У MS-компилятра есть ключик, который говорит, хотим ли мы перехватиывать такие ошибки как исключения или нет. М.б. он там выключен был?

Dasar

У MS-компилятра есть ключик, который говорит, хотим ли мы перехватиывать такие ошибки как исключения или нет. М.б. он там выключен был?
дык, исключение нормально кидается и ловится, вот только при этом деструкторы не отрабатываются

sbs-66

Это с нашего корпоративного портала, т.ч. я тебе промышленную тайну выдаю :)
Заранее прошу прощения, если я пишу общеизвестные вещи, но я сегодня сделал неожиданное для себя открытие - по умолчанию в Visual Studio 2005 конструкция catch-три-точки (catch(... не ловит исключения типа Access Violation, stack overflow и другие системные исключения.
Более того, как говорит MSDN, начиная с Visual C++ 2005, даже в случае перехвата исключений типа Access Violation, не вызываются деструкторы локальных объектов (в том числе наши любимые switcher'ы)! Вообще, при использовании "синхронной модели исключений" компилятор перестаёт рассматривать все системные исключения как "настоящие", не генерирует для них код раскрутки стека, не ловит и т.п.
Теоретически это позволяет компилятору генерить менее раздутый код, поскольку он может теперь считать, что инструкция типа "инкрементировать на единицу int по такому-то адресу" не может вызвать исключений. Компилятор тогда предполагает, что исключения генерируются только инструкцией throw.
Эта настройка у компилятора может быть явно изменена в параметрах (/EHa - асинхронная модель, или /EHsc - синхронная модель). Кажется, в большинстве наших проектов установлена по умолчанию синхронная.
Вообще подобное поведение подразумевалось компилятором давно (см. статью "Exception Handling: Default Synchronous Exception Model" однако в 2005 версии, видимо, оно было наконец-то жёстко реализовано. В старой справке было написано, что в синхронной модели "нельзя полагаться на то, что системные исключения будут ловиться" (If you use /EHs, do not rely on the compiler to catch asynchronous exceptions. а в новой справке написано существенно жёстче: "ситемные исключения ловиться не будут" (If you use /EHs, then your catch clause will not catch asynchronous exceptions. Also, in Visual C++ 2005, all objects in scope when the asynchronous exception is generated will not be destroyed even if the asynchronous exception is handled. Under /EHs, catch(...) will only catch C++ exceptions. Access violations and System.Exception exceptions will not be caught.)
Надеюсь, что скоро будет сформулирована генеральная линия партии по вопросу о том, надо ли включать обработку асинхронных исключений в наших проектах. Интересно было бы узнать, насколько реально велики оказываются накладные расходы.
Вообще, перехват SEH-исключений нужен крайне редко. Если всё-таки нужен, можно написать __try/__except и прямо в фильтре перебросить исключение как C++-ное. Несколько по-хакерски, но работает. set_se_translator игнорируется в режиме /EHs.

erotic

Вроде после endl буфер должен сразу опустошаться.

erotic

Ух, а эти системные еще и перехватывать в принципе возможно? Вообще, по-моему, смысла в этом нет - если у тебя access violation, то это жесткая ошибка, которую надо исправить.

sbs-66

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

smit1

Sutter & Alexandrescu - C++ Coding Standards (по теме - Chapter 62, но и в целом полезно прочесть).
try-блок на весь main по производительности не будет тебе стоить фактически ничего, если ты, конечно, не собираешься его миллион раз рекурсивно вызывать

slonishka

SIGFPE словишь, про исключения все верно, разницы в производительности не будет.

tokuchu

Вроде после endl буфер должен сразу опустошаться.
Да, по '\n' он выводится. Пропустил. На C++ давно прогал да и то особо без "<<". :)

smit1

Да, по '\n' он выводится.
А вот и нет :)
cout << "\n"; // не делает flush
cout << endl; // делает

slonishka

ну если просто написать << '\n', то флаш не гарантируется.
endl — специальная штука для этого.

freezer

если не ошибаюсь, там на самом деле гарантируется, что cerr не будет в процессе вывода ничего аллоцировать (буферов и т.д. поэтому в случае ошибки типа out of memory он отработает, а cout - не факт

tokuchu

А вот и нет :)
cout << "\n"; // не делает flush
cout << endl; // делает
Ну я с точки зрения C всё говорил. Поэтому в таких различиях могу нагнать. Но смысл тот же, что по какому-то концу строки буфер будет чиститься.

erotic

Sutter & Alexandrescu - C++ Coding Standards (по теме - Chapter 62, но и в целом полезно прочесть).
Вроде Бачан о нем нелестно отзывался, а? Кажется, я поэтому и не стал читать)

slonishka

он скучный, майерс веселее при том же контенте (там несколько фишек есть, которых нет у майерса, типа *Rep, но это фигня имхо).

slonishka

натравил, кстати, на днях icc -effc++ на один из проектов.
ворнинги не уместились в xterm*saveLines :grin: :grin:

Garryss

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

~DatabaseMerger {
db1.delete;
db2.delete;
}

Так вот, когда в методе merge произойдет неперехватывамое исключение, то по твоему сценарию будет благополучно вызван деструктор, в результате чего ты потеряешь всё, что у тебя есть. И сразу же начнут задавать твой же вопрос, только наоборот: "А какого фига вызвался деструктор?"
// imho здесь что-то более глубокое с точки зрения логики и простоты компилятора, но уже и этого аргумента будет вполне достаточно

Dasar

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

freezer

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

Dasar

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

freezer

ну а как она должна отрабатывать?

Dasar

есть четыре варианта поведения:
1. продолжать тащить старое исключение
2. потащить новое исключение
3. объединить оба исключения и потащить их оба
4. упасть
мне не понятно, почему был выбран именно 4-ый вариант, а не хотя бы 1 или 2
ps
те же Java и .Net нормально же отрабатывают ситуации, когда исключение выкидывается в finally-блоке

okunek

от первых трех вариантов в следующей ситуации пользы никакой:

class T
{
  //кидаемся в деструкторе
};

T* arr = new T[100000];
delete [] arr;

Dasar

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

freezer

те же Java и .Net нормально же отрабатывают ситуации, когда исключение выкидывается в finally-блоке
а почему по варианту 2, а не в варианту 1?

Dasar

а почему по варианту 2, а не в варианту 1?
потому что это вариант в меньшей степени скрывает информацию.
если тащить первое исключение, то мы никаким образом не узнаем, что у нас было второе исключение.
если же тащить второе исключение, то после его исправления мы увидим, что у нас есть еще одна проблема

okunek

Ну рассмотрим ситуацию, что деструктор упадет в 3-м, 95-м, 1428-м и 56348-м элементе.
Тащим только первое или последнее исключение - не узнаем о других. Тащим все исключения - так деструктор может упасть не в четырех а в четырех миллионах элементах, а память не резиновая.

Dasar

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

freezer

ну вот видишь. Если это ошибка выполнения (кончилась память или стек то нам всё пофиг, даже terminate, а если ошибка программиста, то мы посмотрим на красивое окошко, нажмём "Retry" и будем смотреть в дебагере, что нужно исправить

okunek

> если это ошибка выполнения (например, память кончилась, то в целом нам пофиг на остальные исключения)
Ну фактически вообще пофиг на все исключения.
> после исправления этой ошибки, мы увидим какая ошибка была до этого.
Смахивает на быдло-кодерство "от ошибки до ошибки". :)

freezer

Смахивает на быдло-кодерство "от ошибки до ошибки". :)
так оно и есть. Не ошибается - тот кто ничего не кодит. А только командует (то есть начальник).

Dasar

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

okunek

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

Dasar

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

okunek

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

freezer

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

Dasar

а есть юзер, то да, лучше оставить ни с чем
для кого лучше, и почему?

okunek

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

erotic

тогда не понятно, как завершать программу, если исключения будут кидаться в деструкторах. По стандарту это вызывает terminate, который прекращает раскрутку стека
По-моему вызов деструкторов при аварийном завершении программы и обработка исключения в деструкторе при обработке уже возникшего исключения - две разные вещи, которые не надо смешивать.
У нас есть какая-то стратегия на случай возникновения исключения в деструкторе во время отработки уже существующего исключения? Есть. terminate.
Мы можем в случае аварийного завершения программы (неперехваченного исключения) вызывать деструкторы? Можем. В случае возникновения еще одного исключения в деструкторе руководствуемся правилом 1 - terminate.
P.S. И вообще, у Страуструпа по этому поводу написано, что в архитектурах некоторых систем почти невозможно НЕ вызвать деструкторы при неперехваченном исключении.

freezer

И вообще, у Страуструпа по этому поводу написано, что в архитектурах некоторых систем почти невозможно НЕ вызвать деструкторы при неперехваченном исключении.
причём тут архитектура системы? Это зависит только от рантайма и компилятора. Теоретически, да, можно было бы где-то в коде, вызывающем main как бы поставить catch(... и там уже - terminate

Dasar

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

okunek

Блин, мы благополучно отходим от темы!
> то, что юзер подсунул программе readonly-файл, а разработчик эту ситуация не обработал специальным образом - это ошибка пользователя или программиста?
программиста, конешно

freezer

Это ошибка программиста. Программист не должен допускать того, чтобы система падала при возникновении мало-мальских ошибок. Все такие ошибки следует вылавливать.

okunek

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

Dasar

исключение в деструкторе во время исключения - это не мало-мальская ошибка
если все ошибки идут через исключения, а не как код возврата, то исключение в деструкторе можно получить банальным выдергиванием флешки (имея код в деструкторе: file.Close или выдергивая сетевой провод (имея код: dbConnection.Close
т.е. я правильно понимаю, что ПРАВИЛЬНЫЙ программист должен во все деструкторы пихать try/catch на всякий случай?

Dasar

Это ошибка программиста. Программист не должен допускать того, чтобы система падала при возникновении мало-мальских ошибок. Все такие ошибки следует вылавливать.
дык, кидание исключения - это не есть падение, а C++ - трактует исключение именно как падение.

Dasar

Это ошибка программиста. Программист не должен допускать того, чтобы система падала при возникновении мало-мальских ошибок. Все такие ошибки следует вылавливать.
О, великий гуру, приведи, пожалуйста, свой реальный пример кода, который работает с файлами.
ps
ты действительно делаешь так, чтобы не было исключение, если файл readonly или если файла больше нет?

okunek

Я надеюсь, ты помнишь, что мы говорим о c++ исключениях, а не о SEH, сигналах или о чем бы то ни было еще.
Нет, ты неправильно понимаешь. Не надо в деструктор вставлять код, который приводит к киданию исключений. Если ситуация неизбежна, то нужны try-catch.

okunek

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

Dasar

Я надеюсь, ты помнишь, что мы говорим о c++ исключениях, а не о SEH, сигналах или о чем бы то ни было еще.
Нет, ты неправильно понимаешь. Не надо в деструктор вставлять код, который приводит к киданию исключений. Если ситуация неизбежна, то нужны try-catch.
нихера не понял. особенно при чем здесь seh, сигналы и т.д.
что ты думаешь о следующих утверждениях:
ошибки должны возвращаться через исключения, или через код возврата?
file.Close - должен ли "возвращать" ошибку, если файла уже нет (выдернули флэшку)?
должен ли деструктор file-а вызывать file.Close?
должен ли деструктор file-а оборачиваться в try/catch?
должны ли мы что-то делать специальное при использовании следующее кода, чтобы все работало правильно?

class A
{
File file;
}

okunek

Я много чего думаю по поводу этих утверждений. Напиши что-нибудь по-существу в конце концов.

Dasar

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

okunek

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

Dasar

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

okunek

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

freezer

О, великий гуру, приведи, пожалуйста, свой реальный пример кода, который работает с файлами.
ps
ты действительно делаешь так, чтобы не было исключение, если файл readonly или если файла больше нет?
естественно :) Потому что это - нормальная рабочая ситуация. Там, на самом деле, вообще исключений не бывает ;) Вот конструктор и деструктор в двух реализациях
Win32:

CFile(const char* sFileName, bool bReadOnly)
: hFile(NULL)
, bIsWritable(!bReadOnly)
, nLength(-1)
, nCurrentPos(0)
{
Status;

if(!bReadOnly)
hFile = CreateFile(sFileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);
if(bReadOnly || hFile==INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError;
if(bReadOnly || err==ERROR_ACCESS_DENIED)
{
if(!bReadOnly)
Status("OK", "Can't open file for writing");
bIsWritable = false;
hFile = CreateFile(sFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
err = GetLastError;
if(bReadOnly && err == ERROR_INVALID_PARAMETER)
{
hFile = CreateFile(sFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, NULL, NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
Status("ERROR", "Can't open file");
_err_ << "GetLastError returns " << GetLastError << std::endl;
hFile = 0;
}
}else
{
Status("ERROR", "Can't open file for reading");
_err_ << "GetLastError returns " << err << std::endl;
hFile = 0;
}
}
}else if(err == ERROR_INVALID_PARAMETER)
{
hFile = CreateFile(sFileName, GENERIC_READ|(bReadOnly ? 0 : GENERIC_WRITE 0, NULL, OPEN_EXISTING, NULL, NULL);
if(hFile==INVALID_HANDLE_VALUE)
{
Status("ERROR", "Can't open file");
_err_ << "GetLastError returns " << GetLastError << std::endl;
hFile = 0;
}
}else
{
Status("ERROR", "Can't open file");
_err_ << "Can't open file" << std::endl;
hFile = 0;
}
}
}

~CFile
{
if(hFile!=NULL)
CloseHandle(hFile);
}

Не win32:

CFile(const char* sFileName, bool bReadOnly)
: fno(-1)
, bIsWritable(!bReadOnly)
, nLength(-1)
, nCurrentPos(0)
{
Status;
fno=open(sFileName, O_CREAT|(bReadOnly?O_RDONLY:O_RDWR)|O_LARGEFILE);
if(fno==-1)
{
if(errno==ENOENT)
{
Status("ERROR", "File not found!");
_err_ << "File not found!" << std::endl;
}else if(errno==EMFILE)
{
Status("ERROR", "No more file handlers!");
_err_ << "No more file handlers!" << std::endl;
}
if(errno==EINVAL)
{
Status("ERROR", "Invalid arguments!");
_err_ << "Invalid arguments!" << std::endl;
}else if(errno==EACCES)
{
if(!bReadOnly)
{
Status("OK", "Can't open file for writing");
bIsWritable = false;
fno = open(sFileName, O_CREAT|O_RDONLY|O_LARGEFILE);
if(fno==-1)
{
_err_ << "Can't open read-only file!" << std::endl;
return;
}
}
}else
{
Status("ERROR", (std::string("Can't open file: ")+strerror(errno.c_str;
_err_ << GetStatus << std::endl;
}
}
}

virtual ~CFile
{
if(fno!=-1)
close(fno);
}

kruzer25

Вот конструктор и деструктор в двух реализациях
Пользователь открыл файл и выдернул флэшку, ты умер.

freezer

неа, там система неубиваемая практически :cool:

kruzer25

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

smit1

>ты умер.
В каком месте? В какой строчке кода, то бишь?

smit1

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

freezer

В случае выдёргивания флешки, она будет действовать так же, как будто винт посыпался, т.е. после неудачной записи будет пытаться записать то же самое в другое место. А когда окажется, что писать больше некуда (т.к. никуда не получается) - то запись в данный файл вообще прекратится.
Тут просто какая ситуация. Эта софтина работает в т.ч. на компе без юзера (и может быть даже без монитора или как служба, без interact with desctop. Выдавать там гневный message box со словами "Куда флешку дел, скотина?" там бесполезно
P.S. кажется понял о чём речь. Деструкторы там ни при каких условиях исключение не сгенерят

kruzer25

То есть, вот в такой конструкции:
fh = open(...);
fwrite(fh, ...);
sleep(1000);
close(fh);

не кинет исключение, если во время sleep флэшка исчезнет?
Замечательно. Мы не смогли сохранить записанное - и даже не узнали об этом.
fh1 = open('from.txt');
fh2 = fopen('E:\to.txt');
while(str=fread(fh1 fwrite(fh2, str);
close(fh2);
close(fh1);
file_delete(fh1);

freezer

ну почему ж не узнали? Во-первых, это по логам будет понятно. Во-вторых, данная служба пошлёт оповещение об ошибке (not implemented). В третьих, даже если и не узнаем, это не так уж критично по постановке задачи ;)

Andbar

Пользователь открыл файл и выдернул флэшку, ты умер.
Это вовсе не повод умирать для программы. Тут, имхо, следует понимать, что к моменту, когда ты обнаружишь, что флешка выдернута, всё, что можно сделать - кинуть месадж бокс с сообщением об ошибке записи или в лог сообщение. Остальное - проблемы пользователя (нефиг было флешку дёргать). А значит и ничего делать не надо (естественно, в ситуации, когда запись файла рассматривается как единое действие и не является столь критичным, что его необходимо повторять при отказе, в противном случае работа с файлом должна выделяться в отдельный try-блок внутренний для объекта, из которого берутся данные для записи).
Хз, как в линуксе, но в винде, на уровне API-функций никаких исключений нет и быть не может, возможность исключения при закрытии файла - это уже самодеятельность библиотечных функций, а значит следует поискать альтернативу...
Кстати, на счёт мессаджбокса.. По хорошему, система должна сама ругаться на то, что флешка выдернута при наличии незакрытых файлов, а в таком случае делать вообще ничего не надо.
Что касается интернет-соединения - тут, естественно, не вина пользователя, но и программист, работающий с удалённой информацией на логическом уровне обрабатывать такие вещи не должен (за исключением некоторых задач а значит о повторных попытках подключиться и отправить данные (если это возможно) должен заботиться слой, через который происходит подключение. И ошибку возвращать лишь тогда, когда явно ничего нельзя сделать.

kruzer25

Ты не понял.
Мы открываем файл, пишем в него, закрываем файл; пользователь в это время выдёргивает флэшку.
У библиотечных функций есть только три выхода:
1) Сделать вид, будто ничего не произошло. Это жопа, почему - см.выше.
2) Вернуть какой-то код возврата. И тебе придётся обрабатывать ошибку там, где она возникла - хотя там ты не знаешь, что делать с этой ошибкой.
3) Нормальный вариант - кинуть исключение. Но, если это происходит в деструкторе в C++ - то тебе почему-то опять придётся обрабатывать эту ошибку именно там, где она возникла! Точнее, даже хуже - деструктор позвал одну функцию, она позвала другую, другая повозилась с файлом, кинулось исключение - и какого-то хрена ты должен это исключение в деструкторе ловить, хотя там ты не знаешь ни что это исключение означает (потому что мы далеко от того места, где оно кинулось ни как его обработать.

Andbar

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

Dasar

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

kokoc88

если же по теме, то мне тоже не понятно - зачем отделять seh-исключения от не seh-исключений, и почему не вызываются деструкторы при аварийном завершений программы.
Ты просто не понимаешь, что такое SEH и SOFTWARE, потому что сидишь на C#, где у тебя всё SOFTWARE. Если SEH произошёл из-за прямого доступа к памяти, то вообще неизвестно, что у тебя произойдёт в итоге при вызове деструкторов: ты можешь удалить не тот временный файл или сделать ещё какое-то действие, которое, скорее всего, вызовет другой SEH.
Я считаю, что только полные идиоты врубают отлов аппаратных исключений в catch(... особенно если учитывать, что некоторые реализации того же iostream имеют catch(...) у себя внутри. В таком случае программа начинает падать действительно в непонятных местах по непонятным причинам.
Не надо путать с нехваткой памяти, потому что можно написать программу, которая корректно работает при нехватке памяти, выкидывая исключения.

kokoc88

лучше конечно тупо упасть и оставить пользователя ни с чем.
Ну в C++ это обычно решается установкой per-process обработчика на SEH. После падения программа себя перезапускает и сохраняет краш дамп, который открывается в той же VS по даблклику. :cool:
Надо сказать, что C# не лишён подобных проблем. Во время разработки многие программисты вообще не следят за исключениями. Да, где-то в Application.Run вызывается обработчик, он показывает окошко о том, что произошла ошибка, а класс C# остаётся висеть в каком-то промежуточном состоянии. Бедный пользователь пытается нажать на кнопочку ещё раз, после чего вылетает другое окошко. :) В этом плане Java будет гораздо стабильнее себя вести, потому что она заставляет следить за исключениями.
Если нужны конкретные примеры того, о чём я здесь сказал, могу написать простой код для пояснения. :cool:

kokoc88

file.Close - должен ли "возвращать" ошибку, если файла уже нет (выдернули флэшку)?
Например, System.IO.Stream.Close не выкидывает исключений. Почему код, закрывающий файл на C++ должен это делать? :confused:
Я говорю о том, что на том же C# проблема "выдёргивания флешки" тоже обрабатывается каким-то специальным образом, так что спор ни о чём.

Missi4ka

по умолчанию в Visual Studio 2005 конструкция catch-три-точки (catch(... не ловит исключения типа Access Violation, stack overflow и другие системные исключения.
На самом деле, уровней исключений должно быть два: Exception и Error.
Если искючение типа Exception возникает при обработке ранее выброшенного такого же исключения, то оно ставится в очередь и тащится далше, пока не будут пойманы все предыдущие. Это нужно, чтобы, если мы завершаем с помощью throw работу с файлом и при его закрытии получаем еще одно исключение, мы получили сообщения об ошибках в правильном порядке: "1) Закрываю файл и выхожу спешно по throw. 2) Б...ь! файл не закрывается.", но не наоборот.
Исключения типа Error (напрмер, нехватка памяти или стека во время обработки Exception) не ставятся в очередь, они обрабатываются в приоритетном порядке, если для них определен обработчик, или приводят к завершению программы. Пока не погашен Error, никакие Excception не кидаются и не ловятся, деструкторы не вызываются, так как может быть проблема с ресурсами. Повторыне Error-ы приводят к terminate. Обработчик Error должен просто печатать стек вызовов и очередь исключений, чтобы можно было понять, когда возникла ошибка.
В C++ на уровне языка есть поддержка только одного вида исключений. Мне кажется, определять обработчик Error весьма бессмысленно - все равно это некие простые действия по информированию программиста (не пользователя!) о месте ошибки. При нормальной работе Error-ы возникать не должны. Поэтому можно просто не оборачивать некоторые ошибки (типа Access Violation) в классы исключений, используя для них дефолтный механизм системных Error-ов, например, coredump.
Конечно, вопрос об очередности обработки Exception - спорный. Если мы получили повторное исключение при раскручивании стека, то могут возникнуть проблемы с обработкой текущегою. Например, в блоке catch мы расчитываем на то, что все файлы закрылись при throw и теперь мы хотим их, скажем, копировать. Если закрытие было неуспешным (в очереди стоит Ecxeption по поводу закрытия получим некоррекные данные. Можно пытаться ковырять очередь, можно использовать флаги, поднимаемые при удачном закрытии, можно вешать на каждое Exception свое дерево исключений, произошедших вслед за ним - в любом случае ситуация не очень прозрачная.

kokoc88

Сделать вид, будто ничего не произошло.
Ну вот представь себе, Close в шарпе так и делает. Нагло возвращает void, и не кидает исключений. Ты уже занимаешься предсказанием возникновения bad block-ов на винте.

Dasar

Например, System.IO.Stream.Close не выкидывает исключений.
пиздишь, ты попробуй выдернуть флэшку и посмотри что будет
ps
а будет исключение что-то на тему что volume-а-то уже и нету.

Dasar

Если SEH произошёл из-за прямого доступа к памяти, то вообще неизвестно, что у тебя произойдёт в итоге при вызове деструкторов:
я еще понимаю, почему обращение по null-указателю в C++-должны оформляться как SEH (ну да C++ просто по другому не умеет но почему деление на 0 - это тоже SEH, это я хоть убей понять не могу.

Dasar

Надо сказать, что C# не лишён подобных проблем. Во время разработки многие программисты вообще не следят за исключениями. Да, где-то в Application.Run вызывается обработчик, он показывает окошко о том, что произошла ошибка, а класс C# остаётся висеть в каком-то промежуточном состоянии.
т.е. C++ - такой хороший, потому что в C#- некие идиоты могут написать плохо?
вроде речь идет о том, как за минимум усилий получать стабильную программу даже в условиях когда в программе есть ошибки, а не о том, что некие идиоты могут написать говенную программу

Dasar

класс C# остаётся висеть в каком-то промежуточном состоянии.
и зачем писать такие классы?

Dasar

неа, там система неубиваемая практически
код-то все-таки покажи

freezer

код-то все-таки покажи
весь целиком? там исходников 200к с лишним в этом модуле
P.S. переходи к нам работать - покажу :grin:

kokoc88

пиздишь, ты попробуй выдернуть флэшку и посмотри что будет
ps
а будет исключение что-то на тему что volume-а-то уже и нету.
Значит, возможна маскировка одного исключения другим. Проблемы всё равно есть.

kokoc88

я еще понимаю, почему обращение по null-указателю в C++-должны оформляться как SEH (ну да C++ просто по другому не умеет но почему деление на 0 - это тоже SEH, это я хоть убей понять не могу.
Потому что это аппаратное исключение, которое происходит при выполнении DIV. И ты это прекрасно понимаешь.

Dasar

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

Dasar

Потому что это аппаратное исключение, которое происходит при выполнении DIV. И ты это прекрасно понимаешь.
я не понимаю, почему его нельзя автоматом конвертить в C++-исключение

kokoc88

Надо сказать, что C# не лишён подобных проблем. Во время разработки многие программисты вообще не следят за исключениями. Да, где-то в Application.Run вызывается обработчик, он показывает окошко о том, что произошла ошибка, а класс C# остаётся висеть в каком-то промежуточном состоянии.
т.е. C++ - такой хороший, потому что в C#- некие идиоты могут написать плохо?
вроде речь идет о том, как за минимум усилий получать стабильную программу даже в условиях когда в программе есть ошибки, а не о том, что некие идиоты могут написать говенную программу
Где я написал о том, что кто-то хороший, а кто-то плохой? Идиоты напишут плохо везде. Это раз. Два; ты когда-нибудь пробовал написать несколько операций, которые поддерживают некоторое транзакционное состояние модели, отображаемой в GUI? Когда последовательность операций может приводить к исключению где-то в середине, причём спецификаций на это исключение может и не быть. Если пробовал, то ты понимаешь, что дешевле поймать необработанное исключение, отослать его разработчику и перезапустить программу, даже если она сделана на C#. Три; когда в программе есть ошибки, дают сбои любые подходы.

kokoc88

в остальных случаях мы вообще не имеем информации, что проблема имеет место быть.
Почему же не имеем, мы всё имеем. У нас тоже может быть метод close который при вызове не из деструктора прекрасно отработает. В твоём случае я могу говорить о вызове исключения в момент выполнения файналайзера при сборке мусора. Если хэндл файла закрывается именно в этот момент из-за ошибки программиста, то мы тоже вряд ли узнаем о возникшей проблеме. И по дефолту будет вызван terminate, как и в C++

kokoc88

я не понимаю, почему его нельзя автоматом конвертить в C++-исключение
По той же причине, по которой C++ не проверяет разыменование NULL в сырых указателях. Я тоже не понимаю многих кривых решений в C#, но что поделать, приходится с ними мириться.

Dasar

Два; ты когда-нибудь пробовал написать несколько операций, которые поддерживают некоторое транзакционное состояние модели, отображаемой в GUI? Когда последовательность операций может приводить к исключению где-то в середине, причём спецификаций на это исключение может и не быть. Если пробовал, то ты понимаешь, что дешевле поймать необработанное исключение, отослать его разработчику и перезапустить программу, даже если она сделана на C#.
либо я не до конца понял задачу, либо так лучше не писать вообще
зы
пример какой-нибудь можно?

Dasar

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

kokoc88

и? что предлагается? переселиться в другой мир, где в программах не будет ошибок?
не предлагать подходы, которые лучше уменьшают вред от ошибок?
Ты не привёл подхода, когда уменьшается вред от необработанных исключений.
Я уже привёл пример: трудно следить, чтобы модели данных менялись транзакционно, если исключения могут происходить в любом месте. Поэтому даже в C# при необработанном исключении лучше сообщить об ошибке и завершить программу. Конечно, если ты не хочешь писать тонны кода, который в данном случае может получиться весьма громоздким.

Dasar

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

kokoc88

для выполнения данного правила достаточно менять состояние класса только в конце метода.
Каким способом решить такую задачу я прекрасно понимаю. Речь о том, что так почти никогда не делают. Потому что если выбирать между таким подходом и подходом, который перезапускает программу при неотловленных исключениях; то по критерию скорости и лёгкости разработки чаще выбирают второе.
Кстати, C# тоже довольно часто вызывает terminate, если возникают непредвиденные исключения.

Dasar

Кстати, C# тоже довольно часто вызывает terminate, если возникают непредвиденные исключения.
я знаю всего два случая:
неперехваченное исключение в одном из тредов,
неперехваченное исключения во время FInalize-а

kokoc88

я знаю всего два случая:
неперехваченное исключение в одном из тредов,
неперехваченное исключения во время FInalize-а
Ну так а тема о чём вообще? :) О неперехваченном исключении в одном из тредов.

Dasar

Ну так а тема о чём вообще? О неперехваченном исключении в одном из тредов.
тема(в частности) о том, что не вызываются блоки деструкторов (блоки finally) при неперехваченном исключении.
и о том, что это ведет к проблемам, так как остаются не освобожденными внешние ресурсы
тема (в общем) - о том, какое поведение лучше в случае ошибок (не важно: ошибок выполнения или ошибок разработки)

kokoc88

тема(в частности) о том, что не вызываются блоки деструкторов (блоки finally) при неперехваченном исключении.
и о том, что это ведет к проблемам, так как остаются не освобожденными внешние ресурсы
Не совсем об этом. Она о том, что не вызываются деструкторы, когда происходит terminate.

Dasar

Не совсем об этом. Она о том, что не вызываются деструкторы, когда происходит terminate.
это ты уже юлишь.
какая разница как формулировать?:
1. почему при неперехваченном исключении не вызываются деструкторы?
2. почему при неперехваченном исключении в треде - сразу вызывается terminate, а не сначала деструкторы треда, а потом teminate? (в .net-е кстати реализовано второе поведение)

kokoc88

это ты уже юлишь.
какая разница как формулировать?:
1. почему при неперехваченном исключении не вызываются деструкторы?
2. почему при неперехваченном исключении в треде - сразу вызывается terminate, а не сначала деструкторы треда, а потом teminate? (в .net-е кстати реализовано второе поведение)
Я не юлю. Деструкторы одного треда или все finally блоки отрабатывают? А если в finally вдруг что-то синхронизированное будет стоять? А если в finalizer исключение будет, другие тоже отработают? Я уже сказал, что тема ни о чём: сырой код и манагед. Ничего плохого с точки зрения сырого кода не происходит, я полностью поддерживаю стандарт.
Оставить комментарий
Имя или ник:
Комментарий: