stack_unwinding это маленькая header-only библиотека, которая реализует примитив(class unwinding_indicator) позволяющий определить, был ли вызван деструктор объекта из-за раскрутки стэка или "нормальным" образом
Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate.
В результате, возможно достичь такого же эффекта, как ручная расстановка ".close()" в конце блока, автоматически.
{
File a,b;
// ...
b.close(); // may throw
a.close(); // may throw
}
станет
{
File a,b;
// ...
}
В более общем случае, это поможет созданию "продвинутого" Scope Guard, который не требует ручного вызова commit/release, а также "понимает" исключения из деструкторов.
Язык D имеет scope(success) и scope(failure), которые позволяют задать отложенное действие на конец блока, которое выполняются(либо нет) в зависимости от того, произошёл ли выход из блока по исключению или обычным образом.
В этой библиотеке есть примеры реализаций "scope(success)" и "scope(failure)" (C++03, без лямбд):
В Boost есть библиотека "Scope Exit", которая в примерах использует решение похожее на ручной вызов release/commit (там используется bool переменная).
Цитата из мануала Boost.ScopeExit:
Boost.ScopeExit is similar to scope(exit) feature built into the D programming language.
A curious reader may notice that the library does not implement scope(success) and scope(failure) of the D language.
Unfortunately, these are not possible in C++ because failure or success conditions cannot be determined by calling std::uncaught_exception (see Guru of the Week #47 for details about std::uncaught_exception and if it has any good use at all).
However, this is not a big problem because these two D's constructs can be expressed in terms of scope(exit) and a bool commit variable (similarly to some examples presented in the Tutorial section).
Работоспособность библиотеки была проверенна на: {MSVC2005,MSVC2008,MSVC2010,MSVC2012,GCC 4.1.2,Clang 3.2}x{x32,x64}x{дефолтные настройки}
На данный момент, библиотека реализована поверх платформо-зависимой реализации функции uncaught_exception_count.
uncaught_exception_count — это функция подобная std::uncaught_exception из стандартной библиотеки, но вместо булевского результата возвращает unsigned int, показывающий текущее количество uncaught exceptions
EP>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate.
std::uncaught_exception()?
С другой стороны, это C++11-only.
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, PlusMyTwitterFace, Вы писали:
EP>>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate. PMT>std::uncaught_exception()?
На данный момент, библиотека реализована поверх платформо-зависимой реализации функции uncaught_exception_count.
uncaught_exception_count — это функция подобная std::uncaught_exception из стандартной библиотеки, но вместо булевского результата возвращает unsigned int, показывающий текущее количество uncaught exceptions
есть случаи в которых std::uncaught_exception() возвращает true, но при этом исключение можно кидать, и scope_success должен быть вызван (то есть scope покидается не по исключению).
PMT>С другой стороны, это C++11-only.
std::uncaught_exception — C++98
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>stack_unwinding это маленькая header-only библиотека, которая реализует примитив(class unwinding_indicator) позволяющий определить, был ли вызван деструктор объекта из-за раскрутки стэка или "нормальным" образом
EP>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate.
Гм... а вот, например, при "нормальном" вызове деструкторов элементов массива опасно бросать исключения или нет?
--
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
EP>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate. EP>В результате, возможно достичь такого же эффекта, как ручная расстановка ".close()" в конце блока, автоматически.
Как быть с ситуациями, когда вызывающий деструктор код рассчитывает на то, что из деструктора не полетит исключение?
Тот же Саттер, рассуждая о безопасности исключений приходит в выводу, что полной безопасности не получится достичь,
не имея гарантии отсутствия исключений для некоторых функций. Деструктор входит в число этих функций.
Ну или более конкретно, как разрулится такая ситуация:
есть непустой стандартный контейнер объектов. он вышел из скоупа и позвался его деструктор.
он начал звать деструкторы у содержащихся в нем объектов, которые делают важные вещи, правильно распознают
контекст вызова деструктора (по исключению или нет) и если не по исключению, то могут бросить его.
допустим исключение бросил первый же деструктор объекта. деструкторы остальных объектов не позвались.
в результате имеем утечку. или там это как-то хитро разруливается?
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, _NN_, Вы писали:
_NN>Хотелось бы поддержку C++11 с перемещаемыми объектами. _NN>Сейчас Action должен быть копируемым, что не всегда будет радовать.
да, надо будет использовать boost::move (при наличии boost), или std::move при его наличии..
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, rg45, Вы писали:
R>Гм... а вот, например, при "нормальном" вызове деструкторов элементов массива опасно бросать исключения или нет?
какого именно массива? File f[2]? или delete [] new File[2] ?
На File f[2] индикатор сейчас работает неправильно — current_exception_count изменяется уже после удаления всего массива(по крайней мере на MSVC).
Есть и другие ограничения. Например когда объект "плавает", то есть создаётся при stack unwinding, но переживает раскрутку(например в глобальном объекте)
Надо добавить секцию limitations..
Re[3]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, rg45, Вы писали:
R>>Гм... а вот, например, при "нормальном" вызове деструкторов элементов массива опасно бросать исключения или нет?
EP>какого именно массива? File f[2]? или delete [] new File[2] ? EP>На File f[2] индикатор сейчас работает неправильно — current_exception_count изменяется уже после удаления всего массива(по крайней мере на MSVC). EP>Есть и другие ограничения. Например когда объект "плавает", то есть создаётся при stack unwinding, но переживает раскрутку(например в глобальном объекте) EP>Надо добавить секцию limitations..
Я имел ввиду, что при выбрасывании исключения из деструктора какого-либо элемента массива, деструкторы остальных элементов просто не будут вызваны, со всеми вытекающими. И никаким счетчиком проблема не решается, как бы он ни работал. Это актуально вне зависимости от области хранения массива и к стандартным контейнерам все сказанное относится в равной мере.
--
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, UniqueRSDN, Вы писали:
URS>Как быть с ситуациями, когда вызывающий деструктор код рассчитывает на то, что из деструктора не полетит исключение?
Я думал над этим — проблема с транзитивностью действительно есть. Если объект A использует B, который вдруг начал использовать C кидающий исключения из деструктора — то теперь и B и A, и все кто их агрегирует/наследуют, кидают исключения из деструкторов. Причём есть много кода, который не насчитан на кидающие деструкторы.
Похожая проблема существует с мутабельностью.
Сейчас, безопасно(?) можно использовать объект с кидающими деструкторами в блоке кода, но не как член или родитель класса. Например можно кидать исключения в action у scope(success).
URS>Тот же Саттер, рассуждая о безопасности исключений приходит в выводу, что полной безопасности не получится достичь, URS>не имея гарантии отсутствия исключений для некоторых функций. Деструктор входит в число этих функций.
По поводу кидающих деструкторов, недавно вышла такая статья: Evil, or Just Misunderstood?
URS>Ну или более конкретно, как разрулится такая ситуация: URS>есть непустой стандартный контейнер объектов. он вышел из скоупа и позвался его деструктор. URS>он начал звать деструкторы у содержащихся в нем объектов, которые делают важные вещи, правильно распознают URS>контекст вызова деструктора (по исключению или нет) и если не по исключению, то могут бросить его. URS>допустим исключение бросил первый же деструктор объекта. деструкторы остальных объектов не позвались. URS>в результате имеем утечку. или там это как-то хитро разруливается?
Если речь идёт именно о стандартном контейнере, то тут особо ничего не поделаешь.
Если о контейнере вообще — то можно следуя идеологии кидания исключения по возможонсти, поймать первое исключение, остальные проглотить, а потом throw;
При текущем положении дел, кидание исключений из деструкторов опасное занятие, даже если не брать во внимание технические проблемы — большинство кода просто не рассчитано на исключения из деструкторов. Я не агитирую за необдуманное кидание исключений из деструкторов налево и направо (несмотря на название топика )
Но в то же время, помимо непосредственно деструкции и освобождения ресурсов, на деструкторы навешиваются разного рода отложенные действия, например тот же flush, которые могут фэйлится. Проглатывание всех этих фэйлов по-умолчанию — решение далёкое от идеала.
Например, текущая ситуация по-умолчанию(без явных вызовов flush/close/etc, без логирования фэйла(которое может также сфэйлится)):
1. Первый деструктор — фейл, no action
2. Второй деструктор — фейл, no action
3. Обработка молча завершена
или с киданием исключения по возможности:
1. Первый деструктор — фейл, кидает исключение
2. Второй деструктор во время раскрутки — фэйл, кидать нельзя
3. catch первого исключения выше по call-stack'у, в месте ответственном за их обработку
4. информирование пользователя
5. пользователь устраняет причины, и запускает обработку заново
6. Первый деструктор — success
7. Второй деструктор — фэйл (а может и не фэйл, если зависело от первого), кидает исключение
... пользователь узнаёт об этом, исправляет и запускает заново.
Ведь вполне возможный сценарий.
Что лучше?
Re[3]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
я не знаю что лучше. сложно все как-то получается.
можно придумать кейс, в котором запуск по новой фейлится еще раньше, чем первый фейл, потому что деструкторы не отработали до конца.
пока что все выглядит так, что для того, чтобы все корректно работало с кидающимися деструкторами, нужно специальным образом
писать код, учитывающий факт бросания исключений из деструкторов. в случае не кидающихся деструкторов код тоже нужно правильно
организовать. вопрос в том, что будет проще. пока что особого профита я не увидел и предпочту вариант, в котором деструкторы
все-таки не кидают ничего. эта дорожка хотябы протоптана.
Re[4]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, rg45, Вы писали:
R>Я имел ввиду, что при выбрасывании исключения из деструктора какого-либо элемента массива, деструкторы остальных элементов просто не будут вызваны, со всеми вытекающими.
#include <iostream>
#include <exception>
using namespace std;
struct T
{
~T()
{
cout << "~T()";
throw 1;
}
};
int main()
{
try
{
T t[10];
}
catch(int)
{
cout << "catch";
}
return 0;
}
на этом коде MSVC2008 выдал ~T()~T() и крашнулся.
а gcc-4.3.4 выдал ~T()catch и нормально завершился.
надо проверить ISO: это баг MSVC или просто UB
Re[4]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
[...] URS>вопрос в том, что будет проще. пока что особого профита я не увидел и предпочту вариант, в котором деструкторы URS>все-таки не кидают ничего. эта дорожка хотябы протоптана.
всё правильно написано
А что насчёт объектов, которые создаются и живут только в scope кода, то есть не члены, не базы, не хранятся в контейнерах?
Допустим создаём std::ofstream, и сразу рядом с ним scope action на success (выполняет заданное действие в деструкторе, если не stack unwinding), в котором взводим exceptions и дёргаем close (который соответственно может кинуть).
Либо создаём свою небольшую обвёртку, которую по соглашению можно использовать только в scope кода.
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
Мое бескомпромиссное имхо — кидать из деструкторов пытаются те, кто не умеет их готовить. Деструктор — это то, что вызывается, когда объект разрушается, так? А что значит исключение? Это значит, что функция не смогла выполнить пост-условие, то есть, объект не может быть разрушен ни при каких обстоятельствах. Но это же абсурд! Что значит — не смог быть разрушен? И что теперь с ним делать? Как программе обходиться с объектом, который не может (а должен) быть разрушен, а? С точки зрения бизнес-логики и здравого смысла? Представь себе, что delete вдруг начал бы кидать исключения. Что это вообще может означать?
Нет, кидать из деструкторов — абсурд. Если какая-то операция принципиально может обломиться, ей не место в деструкторе.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
[...] V>Нет, кидать из деструкторов — абсурд. Если какая-то операция принципиально может обломиться, ей не место в деструкторе.
Помимо непосредственно освобождения ресурсов(которое по своей сути не фэйлится, за очень редким исключением), на деструкторы навешиваются разного рода отложенные действия, которые могут фэйлится.
Яркий пример это flush у класса типа "File".
Один из углов под которыми можно рассматривать кидающие деструкторы:
1. Освобождение ресурсов не должно фейлится (“letting go of a resource” must never fail Herb Sutter http://cpp-next.com/archive/2012/08/evil-or-just-misunderstood/)
2. Освобождение ресурсов должно произойти в любом случае — утечек быть не должно
3. Поддержка отложенных действий (flush) — желательна. Такие действия могут фэйлится
4. Если отложенное действие фэйлится — пользователь должен узнать об этом. Проглатывание исключений (потеря информации) — не является хорошим решением.
Один из подходов который удовлетворяет данным требованиям (но не полагающийся на одновременное распространение нескольких исключений (которое естественно не поддерживается) ), состоит в том, что отложенные действия вызываются только в том случае, когда деструктор вызван не из-за раскрутки стэка.
Это приводит к двух-фазной деструкции:
class RAII_Deffered
{
bool fail_on_flush;
public:
// Normal contrustor
RAII_Deffered(bool fail_on_flush_) : fail_on_flush(fail_on_flush_)
{
cout << "acquiring resource" << endl;
}
// Release part of destructor.
// Herb Sutter: "letting go of a resource" must never fail
// (http://cpp-next.com/archive/2012/08/evil-or-just-misunderstood/)
TWO_STAGE_DESTRUCTOR_RELEASE(RAII_Deffered)
{
cout << "release resource" << endl;
}
// Deferred part of destructor. May fail(for instance fflush).
// Called when object is destroyed due to normal flow, not stack unwinding
TWO_STAGE_DESTRUCTOR_DEFERRED(RAII_Deffered)
{
cout << "flush pending actions on resource" << endl;
if(fail_on_flush) throw 1;
}
};
И пример использования.
ИМХО — такой подход имеет смысл. Не вызывание отложенных действий во время раскрутки, делает их похожими на обычные действия которые следуют за throw — обычные действия игнорятся (естественно) во время раскрутки.
V>Мое бескомпромиссное имхо — кидать из деструкторов пытаются те, кто не умеет их готовить.
Как будешь готовить класс "File"? Где будет flush?
1. Во время каждой операции?
2. В деструкторе? как сообщать об ошибке?
3. В отдельной функции close/flush? Обязан ли пользователь её всегда вызывать? Вызывает ли её деструктор?
Re[3]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
EP>Помимо непосредственно освобождения ресурсов(которое по своей сути не фэйлится, за очень редким исключением), на деструкторы навешиваются разного рода отложенные действия, которые могут фэйлится. EP>Яркий пример это flush у класса типа "File".
Флаш не должен фейлиться, потому, что в нормальных условиях не фейлиться fflush. У него, конечно, есть набор кодов ошибок, но все они должны проверяться ДО вызова деструктора. Среди всех возможных ошибок fflush нет ни одной, которую нельзя было бы проверить заранее — при последней записи, например.
Еще раз — все операции, которые могут завершиться неуспешно, выполняются ДО вызова деструктора.
EP>4. Если отложенное действие фэйлится — пользователь должен узнать об этом. Проглатывание исключений (потеря информации) — не является хорошим решением.
Не понимаю, что тут можно сделать. Возьмем случай кидающего деструктора.
class A {... ~A() { ... throw ... } };
void foo() {
A a;
}
void bar() {
try {
foo();
} catch(...) {
// И ЧТО?
}
}
И что в catch? Объект a — он удален или где? Что вообще тут делать? Написать "случилась фигня"? Ну в этом случае можно безо всяких исключений terminate позвать прямо из деструктора. Дешево и сердито. EP>Как будешь готовить класс "File"? Где будет flush? EP>1. Во время каждой операции?
В нигде. Будет как отдельный метод. Из деструктора звать fflush не буду, только fclose.
EP>2. В деструкторе? как сообщать об ошибке?
Никак. Fflush реально не возвращает ошибок.
EP>3. В отдельной функции close/flush? Обязан ли пользователь её всегда вызывать? Вызывает ли её деструктор?
См. выше.
Да здравствует мыло душистое и веревка пушистая.
Re[4]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
... EP>>Как будешь готовить класс "File"? Где будет flush? EP>>1. Во время каждой операции? V>В нигде. Будет как отдельный метод. Из деструктора звать fflush не буду, только fclose.
просвещайся. http://www.cplusplus.com/reference/clibrary/cstdio/fclose/ On failure, EOF is returned.
EP>>2. В деструкторе? как сообщать об ошибке? V>Никак. Fflush реально не возвращает ошибок.
гениально. программы не совершают ошибок. эй юзер, воткни обратно флешку !
EP>>3. В отдельной функции close/flush? Обязан ли пользователь её всегда вызывать? Вызывает ли её деструктор? V>См. выше.
удачи.
Re[5]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
V>>В нигде. Будет как отдельный метод. Из деструктора звать fflush не буду, только fclose. SD>просвещайся. SD>http://www.cplusplus.com/reference/clibrary/cstdio/fclose/ SD>On failure, EOF is returned.
А почему ты думаешь, что я этого не знаю?? Возьми и посмотри, какие для того могут быть причины. Они все могут быть проверены заранее.
V>>Никак. Fflush реально не возвращает ошибок. SD>гениально. программы не совершают ошибок. эй юзер, воткни обратно флешку !
Программы действительно не совершают ошибок. Ошибки совершают некоторые программисты — например, когда начинают швыряться исключениями из деструктора.
SD>удачи.
И тебе не грустить.
Да здравствует мыло душистое и веревка пушистая.
Re[4]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
Здравствуйте, Vamp, Вы писали:
V>Среди всех возможных ошибок fflush нет ни одной, которую нельзя было бы проверить заранее — при последней записи, например.
Откуда такая гарантия, учитывая что запись буферизованная?
EP>>4. Если отложенное действие фэйлится — пользователь должен узнать об этом. Проглатывание исключений (потеря информации) — не является хорошим решением. V>Не понимаю, что тут можно сделать. Возьмем случай кидающего деструктора.
[...] V>И что в catch? Объект a — он удален или где? Что вообще тут делать? Написать "случилась фигня"?
То же самое, что и при обычных исключениях
Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
Использовать исключения, для кода, в котором каждый вызов функции обвёрнут в try/catch — глупо, уж лучше коды возврата (или что ты хотел показать свои примером?).
V>Ну в этом случае можно безо всяких исключений terminate позвать прямо из деструктора. Дешево и сердито.
После обычных исключений, ты тоже стреляешь программе в голову?
EP>>Как будешь готовить класс "File"? Где будет flush? EP>>1. Во время каждой операции? V>В нигде. Будет как отдельный метод. Из деструктора звать fflush не буду, только fclose. EP>>2. В деструкторе? как сообщать об ошибке? V>Никак. Fflush реально не возвращает ошибок. EP>>3. В отдельной функции close/flush? Обязан ли пользователь её всегда вызывать? Вызывает ли её деструктор? V>См. выше.
Ну вот абстрактный пример, два файла пишутся параллельно:
У fsome, следующие постусловие — данные записаны в оба файла. Если fsome не может достичь своего постусловия — то кидается исключение.
Что ты предлагаешь делать в местах "Fail #1" и "Fail #2"?
Моё вариант:
void fsome(/* ... */)
{
OutFile a("a"),b("b");
a.write("Hello");
b.write("World");
// ...
// b.~OutFile():
// 1. flush - Fail #1, throw exception
// 2. fclose - called anyway
// a.~OutFile() - called due to stack unwinding:
// 1. flush is not called, due to stack unwinding
// 2. fclose - called anyway
}
Re[5]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
EP>Откуда такая гарантия, учитывая что запись буферизованная?
Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.
EP>То же самое, что и при обычных исключениях EP>Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
При обычных исключениях объект не создан, и никакой работы еще не проделано. То есть если файл нельзя создать — то мы кидаем исключение (хотя я бы и в этом случае обошелся кодом возврата) — и выдаем окошечко, файл не создан, выбери другой.
А когда у тебя файл не закрыт — что ты будешь делать? То есть, ты писал-писал (особенно хорошо, если данные приходили по сети) — и вдруг опаньки? Что РАЗУМНОГО ты можешь сделать в этой ситуации?
EP>После обычных исключений, ты тоже стреляешь программе в голову?
Нет. Но см. выше.
EP>>>Как будешь готовить класс "File"? Где будет flush?
EP>Ну вот абстрактный пример, два файла пишутся параллельно: EP>
EP>У fsome, следующие постусловие — данные записаны в оба файла. Если fsome не может достичь своего постусловия — то кидается исключение.
Оставь ты flush в покое. Как думаешь, почему у fstream деструктор не швыряет, а?