Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: Evgeny.Panasyuk Россия  
Дата: 27.09.12 19:02
Оценка: 194 (8)
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, без лямбд):
{
    cout << "Case #1: stack unwinding" << endl;
    scope_action exit=make<scope_exit>(Print(" exit"));
    scope_action failure=make<scope_failure>(Print(" failure")); 
    scope_action success=make<scope_success>(Print(" success"));
    throw 1;
}


В 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

Ссылки:
* http://www.boost.org/doc/libs/1_51_0/libs/scope_exit/doc/html/index.html
* http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2012/Three-Unlikely-Successful-Features-of-D
* http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758
* http://www.gotw.ca/gotw/047.htm
* https://github.com/panaseleus/stack_unwinding
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: PlusMyTwitterFace  
Дата: 27.09.12 20:56
Оценка:
EP>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate.

std::uncaught_exception()?

С другой стороны, это C++11-only.
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 27.09.12 21:03
Оценка:
Здравствуйте, 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++
От: _NN_ www.nemerleweb.com
Дата: 28.09.12 05:02
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

Круто !
Как-то давно была нужна фишка с scope(success) , в итоге пришлось через флаг делать.

Хотелось бы поддержку C++11 с перемещаемыми объектами.
Сейчас Action должен быть копируемым, что не всегда будет радовать.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: rg45 СССР  
Дата: 28.09.12 07:25
Оценка:
Здравствуйте, 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++
От: UniqueRSDN  
Дата: 28.09.12 07:45
Оценка: +3
EP>Библиотека позволяет определить в каких случаях реально опасно кидать исключение из деструктора. То есть когда исключение покинувшее деструктор может привести к вызову std::terminate.
EP>В результате, возможно достичь такого же эффекта, как ручная расстановка ".close()" в конце блока, автоматически.

Как быть с ситуациями, когда вызывающий деструктор код рассчитывает на то, что из деструктора не полетит исключение?
Тот же Саттер, рассуждая о безопасности исключений приходит в выводу, что полной безопасности не получится достичь,
не имея гарантии отсутствия исключений для некоторых функций. Деструктор входит в число этих функций.
Ну или более конкретно, как разрулится такая ситуация:
есть непустой стандартный контейнер объектов. он вышел из скоупа и позвался его деструктор.
он начал звать деструкторы у содержащихся в нем объектов, которые делают важные вещи, правильно распознают
контекст вызова деструктора (по исключению или нет) и если не по исключению, то могут бросить его.
допустим исключение бросил первый же деструктор объекта. деструкторы остальных объектов не позвались.
в результате имеем утечку. или там это как-то хитро разруливается?
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 28.09.12 07:46
Оценка:
Здравствуйте, _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
От: Evgeny.Panasyuk Россия  
Дата: 28.09.12 09:00
Оценка:
Здравствуйте, 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
От: rg45 СССР  
Дата: 28.09.12 10:21
Оценка: +1
Здравствуйте, 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
От: Evgeny.Panasyuk Россия  
Дата: 28.09.12 11:30
Оценка:
Здравствуйте, 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
От: UniqueRSDN  
Дата: 28.09.12 12:02
Оценка: 13 (2) +4
я не знаю что лучше. сложно все как-то получается.
можно придумать кейс, в котором запуск по новой фейлится еще раньше, чем первый фейл, потому что деструкторы не отработали до конца.
пока что все выглядит так, что для того, чтобы все корректно работало с кидающимися деструкторами, нужно специальным образом
писать код, учитывающий факт бросания исключений из деструкторов. в случае не кидающихся деструкторов код тоже нужно правильно
организовать. вопрос в том, что будет проще. пока что особого профита я не увидел и предпочту вариант, в котором деструкторы
все-таки не кидают ничего. эта дорожка хотябы протоптана.
Re[4]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 28.09.12 12:11
Оценка:
Здравствуйте, 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
От: Evgeny.Panasyuk Россия  
Дата: 28.09.12 13:20
Оценка:
Здравствуйте, UniqueRSDN, Вы писали:

[...]
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++
От: Vamp Россия  
Дата: 01.10.12 17:14
Оценка:
Мое бескомпромиссное имхо — кидать из деструкторов пытаются те, кто не умеет их готовить. Деструктор — это то, что вызывается, когда объект разрушается, так? А что значит исключение? Это значит, что функция не смогла выполнить пост-условие, то есть, объект не может быть разрушен ни при каких обстоятельствах. Но это же абсурд! Что значит — не смог быть разрушен? И что теперь с ним делать? Как программе обходиться с объектом, который не может (а должен) быть разрушен, а? С точки зрения бизнес-логики и здравого смысла? Представь себе, что delete вдруг начал бы кидать исключения. Что это вообще может означать?
Нет, кидать из деструкторов — абсурд. Если какая-то операция принципиально может обломиться, ей не место в деструкторе.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 17:41
Оценка:
Здравствуйте, Vamp, Вы писали:

[...]
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;
    }
};

которая имеет следующую семантику:
~Foo(bool due_to_unwinding)
{
    try
    {
        if(!due_to_unwinding) deferred_part_of_destructor();
    }
    catch(...)
    {
        release_part_of_destructor();
        throw;
    }
    release_part_of_destructor();
}

И пример использования.
ИМХО — такой подход имеет смысл. Не вызывание отложенных действий во время раскрутки, делает их похожими на обычные действия которые следуют за throw — обычные действия игнорятся (естественно) во время раскрутки.

V>Мое бескомпромиссное имхо — кидать из деструкторов пытаются те, кто не умеет их готовить.


Как будешь готовить класс "File"? Где будет flush?
1. Во время каждой операции?
2. В деструкторе? как сообщать об ошибке?
3. В отдельной функции close/flush? Обязан ли пользователь её всегда вызывать? Вызывает ли её деструктор?
Re[3]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Vamp Россия  
Дата: 01.10.12 17:56
Оценка: +1 -1
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
От: SleepyDrago Украина  
Дата: 01.10.12 18:47
Оценка:
Здравствуйте, Vamp, Вы писали:

...
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
От: Vamp Россия  
Дата: 01.10.12 18:55
Оценка: +1
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
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 19:13
Оценка:
Здравствуйте, 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>См. выше.

Ну вот абстрактный пример, два файла пишутся параллельно:
void fsome(/* ... */)
{
    OutFile a("a"),b("b");
    a.write("Hello");
    b.write("World");
    // ...
    b.flush(); // Fail #1
    a.flush(); // Fail #2
}

У 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
От: Vamp Россия  
Дата: 01.10.12 19:27
Оценка: +1
EP>Откуда такая гарантия, учитывая что запись буферизованная?
Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.

EP>То же самое, что и при обычных исключениях

EP>Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
При обычных исключениях объект не создан, и никакой работы еще не проделано. То есть если файл нельзя создать — то мы кидаем исключение (хотя я бы и в этом случае обошелся кодом возврата) — и выдаем окошечко, файл не создан, выбери другой.
А когда у тебя файл не закрыт — что ты будешь делать? То есть, ты писал-писал (особенно хорошо, если данные приходили по сети) — и вдруг опаньки? Что РАЗУМНОГО ты можешь сделать в этой ситуации?

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

Нет. Но см. выше.

EP>>>Как будешь готовить класс "File"? Где будет flush?


EP>Ну вот абстрактный пример, два файла пишутся параллельно:

EP>
EP>void fsome(/* ... */)
EP>{
EP>    OutFile a("a"),b("b");
EP>    a.write("Hello");
EP>    b.write("World");
EP>    // ...
EP>    b.flush(); // Fail #1
EP>    a.flush(); // Fail #2
EP>}
EP>

EP>У fsome, следующие постусловие — данные записаны в оба файла. Если fsome не может достичь своего постусловия — то кидается исключение.
Оставь ты flush в покое. Как думаешь, почему у fstream деструктор не швыряет, а?
Да здравствует мыло душистое и веревка пушистая.
Re[6]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 20:40
Оценка:
Здравствуйте, Vamp, Вы писали:

EP>>Откуда такая гарантия, учитывая что запись буферизованная?

V>Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.

внезапно закончилось место в разделе

EP>>То же самое, что и при обычных исключениях

EP>>Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
V>При обычных исключениях объект не создан, и никакой работы еще не проделано. То есть если файл нельзя создать — то мы кидаем исключение (хотя я бы и в этом случае обошелся кодом возврата) — и выдаем окошечко, файл не создан, выбери другой.

хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?

V>А когда у тебя файл не закрыт — что ты будешь делать? То есть, ты писал-писал (особенно хорошо, если данные приходили по сети) — и вдруг опаньки? Что РАЗУМНОГО ты можешь сделать в этой ситуации?


Как это относится к кидающим/не кидающим деструкторам? Эта ситуация может случится и без них

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

V>Нет. Но см. выше.

вижу — у тебя исключения только в конструкторах, действительно объекты-то не созданы

EP>>>>Как будешь готовить класс "File"? Где будет flush?

EP>>Ну вот абстрактный пример, два файла пишутся параллельно:
EP>>
EP>>void fsome(/* ... */)
EP>>{
EP>>    OutFile a("a"),b("b");
EP>>    a.write("Hello");
EP>>    b.write("World");
EP>>    // ...
EP>>    b.flush(); // Fail #1
EP>>    a.flush(); // Fail #2
EP>>}
EP>>

EP>>У fsome, следующие постусловие — данные записаны в оба файла. Если fsome не может достичь своего постусловия — то кидается исключение.
EP>>Что ты предлагаешь делать в местах "Fail #1" и "Fail #2"?
V>Оставь ты flush в покое.

Ты предложил вариант с явным вызовом, отдельного flush:

V>В нигде. Будет как отдельный метод. Из деструктора звать fflush не буду, только fclose.

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

V>Мое бескомпромиссное имхо — кидать из деструкторов пытаются те, кто не умеет их готовить.


V>Как думаешь, почему у fstream деструктор не швыряет, а?


наверное боится std::terminate, а is_unwinding у него нет.
Проглатывание ошибок — очень плохое решение.
Re[7]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Andrew S Россия http://alchemy-lab.com
Дата: 01.10.12 21:26
Оценка: +1
EP>>>Откуда такая гарантия, учитывая что запись буферизованная?
V>>Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.

EP>внезапно закончилось место в разделе


EP>>>То же самое, что и при обычных исключениях

EP>>>Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
V>>При обычных исключениях объект не создан, и никакой работы еще не проделано. То есть если файл нельзя создать — то мы кидаем исключение (хотя я бы и в этом случае обошелся кодом возврата) — и выдаем окошечко, файл не создан, выбери другой.

EP>хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?


Как ни странно, но мой опыт говорит, что при работе с объектом уровня File исключение не должно произойти нигде. Это примитив слишком низкого уровня абстракции, чтобы швыряться исключениям. Например, открытие файла очень часто используется для проверки его существования на предмет создания временного. Кидаться в этом случае исключением ну никак нельзя — это логика, а не обработка ошибок.

Касательно fflush. Действительно, делать fflush совместно с fclose безусловно нельзя — далеко не всем (а точнее, вообще не всем) клиентам сервиса File это требуется. Если рассматривать жизненные кейзы, то становится очевидно, что логика принятия решения о вызове или не вызове fflush — это логика уровня абстракции сильно выше, чем File. Например, с т.з. производительности эти операции просто нельзя совмещать на данном уровне.
Касательно fclose. К сожалению, что fclose, что CloseHandle, не позволяют клиенту сделать какие-либо вменяемые действия по восстановлению на уровне File. Все что реально может клиент — констатировать проблему (VERIFY или trace), и забыть про этот описатель, либо попытаться откатить операцию и сделать все наново (уровень сильно выше File). Для действий второго рода у объектов уровня File обычно есть метод Close, которые позволяет при необходимости получить код ошибки и обработать по своему вкусу.
Как все это можно автоматизировать и не вызывать методы File ручками? Обычное решение в этом случае — делается отдельный объект более высокого, чем File, уровня абстракции, который прямо или косвенно использует File и реализует логику прикладного уровня, в т.ч. реакцию на ошибку fflush, например, запись в трейс.
В целом, выкидывание исключения из деструктора представляется достаточно опасной, а главное, бесполезной концепцией (классическую проблему — что делать, если при выкидывании исключения в деструкторе еще исключение — какое должно пролететь и как это обработать — обычно нельзя решить на уровне объекта, уничтожаемого в результате свертки стека). Нужно ли давать пользователю возможность отстрелить себе ногу, если можно (и лучше) этого не делать?
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[7]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Vamp Россия  
Дата: 01.10.12 21:28
Оценка:
V>>Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.
EP>внезапно закончилось место в разделе
Ага. И что ты можешь сделать в этом случае? Что твой гипотетический catch сделает?

EP>хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?

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

EP>Как это относится к кидающим/не кидающим деструкторам? Эта ситуация может случится и без них

Может. И деструктор тут не поможет. Отсюда еще раз — деструктор в принципе не должен выполнять действия, могущие вернуть код ошибки. Потому что есть is_unwinding, что нет — восстановиться в этом случае нельзя.
Да здравствует мыло душистое и веревка пушистая.
Re[8]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Vamp Россия  
Дата: 01.10.12 21:30
Оценка:
AS>В целом, выкидывание исключения из деструктора представляется достаточно опасной, а главное, бесполезной концепцией (классическую проблему — что делать, если при выкидывании исключения в деструкторе еще исключение — какое должно пролететь и как это обработать — обычно нельзя решить на уровне объекта, уничтожаемого в результате свертки стека). Нужно ли давать пользователю возможность отстрелить себе ногу, если можно (и лучше) этого не делать?
Во-во, и именно эту простую мысль я пытаюсь донести до автора.
Да здравствует мыло душистое и веревка пушистая.
Re[8]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 21:46
Оценка:
Здравствуйте, Andrew S, Вы писали:

EP>>хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?

AS>Как ни странно, но мой опыт говорит, что при работе с объектом уровня File исключение не должно произойти нигде. Это примитив слишком низкого уровня абстракции, чтобы швыряться исключениям.

пусть будет не файл, пусть будет что-то, с отложенным действием. Семантика:
{
    Something a,b;
    // ...
    b.deferred(); // may throw
    a.deferred(); // may throw
}

Почему бы не использовать предложенный метод, и вместо этого писать:
{
    Something a,b;
    // ...
}

?

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


Первое сообщение топика читал? А документацию?
А про двух-фазную деструкцию
Автор: Evgeny.Panasyuk
Дата: 01.10.12
?

AS>Нужно ли давать пользователю возможность отстрелить себе ногу, если можно (и лучше) этого не делать?


http://www.rsdn.ru/forum/cpp/4909480.1
Автор: Evgeny.Panasyuk
Дата: 28.09.12

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

Re[8]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 21:58
Оценка:
Здравствуйте, Vamp, Вы писали:

V>>>Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.

EP>>внезапно закончилось место в разделе
V>Ага. И что ты можешь сделать в этом случае? Что твой гипотетический catch сделает?

например, сказать пользователю, он примет меры, а потом повторить заново.

EP>>хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?

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

Я пытаюсь понять, ты говоришь о try/catch исключений вокруг каждого вызова? Ещё раз:

Их обработка обычно происходит несколькими уровнями выше, чем непосредственно код в котором они произошли, и все объекты тоже уже удаленны. Хотя бы потому, что во многих случаях в том месте где они произошли — недостаточно информации для их обработки/исправления. Это одна из главных фич исключений.
Использовать исключения, для кода, в котором каждый вызов функции обвёрнут в try/catch — глупо, уж лучше коды возврата (или что ты хотел показать свои примером?).


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

V>>А когда у тебя файл не закрыт — что ты будешь делать? То есть, ты писал-писал (особенно хорошо, если данные приходили по сети) — и вдруг опаньки? Что РАЗУМНОГО ты можешь сделать в этой ситуации?

EP>>Как это относится к кидающим/не кидающим деструкторам? Эта ситуация может случится и без них
V>Может. И деструктор тут не поможет. Отсюда еще раз — деструктор в принципе не должен выполнять действия, могущие вернуть код ошибки. Потому что есть is_unwinding, что нет — восстановиться в этом случае нельзя.

А как получилось это "отсюда". Таким же маневром можно сказать, что любой код "в принципе не должен выполнять действия, могущие вернуть код ошибки", так как в нём тоже может произойти эта ситуация.
Re[9]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 01.10.12 22:05
Оценка:
Здравствуйте, Vamp, Вы писали:

AS>>В целом, выкидывание исключения из деструктора представляется достаточно опасной, а главное, бесполезной концепцией (классическую проблему — что делать, если при выкидывании исключения в деструкторе еще исключение — какое должно пролететь и как это обработать — обычно нельзя решить на уровне объекта, уничтожаемого в результате свертки стека). Нужно ли давать пользователю возможность отстрелить себе ногу, если можно (и лучше) этого не делать?

V>Во-во, и именно эту простую мысль я пытаюсь донести до автора.

Какую именно?
1. два исключения через один стэк-фрейм? — ну так is_unwinding в первом сообщении описан.
2. отложенные действия на объектах, которые могут сфейлится не нужны, либо пользователь не должен знать об этих фэйлах?
3. is_unwinding не нужен?
4. вызов std::terminate, то есть беспощадное убийство программы, это нормально? И не нужно пытаться исправить ситуацию?
Re[9]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Andrew S Россия http://alchemy-lab.com
Дата: 02.10.12 21:15
Оценка:
EP>>>хм, то есть при работе с объектом типа "File", исключение может произойти только в конструкторе?
AS>>Как ни странно, но мой опыт говорит, что при работе с объектом уровня File исключение не должно произойти нигде. Это примитив слишком низкого уровня абстракции, чтобы швыряться исключениям.

EP>пусть будет не файл, пусть будет что-то, с отложенным действием. Семантика:

EP>
EP>{
EP>    Something a,b;
EP>    // ...
EP>    b.deferred(); // may throw
EP>    a.deferred(); // may throw
EP>}
EP>

EP>Почему бы не использовать предложенный метод, и вместо этого писать:
EP>
EP>{
EP>    Something a,b;
EP>    // ...
EP>}
EP>

EP>?

Хотелось бы понять, зачем. Если у Something нет других методов, кроме deferred, тогда объект не нужен — это функция. Если есть — тогда принимать решение по контракту того, что должно происходить в деструкторе объекта при ошибке освобождения ресурса должен либо внешний код на этом же уровне (т.е. исключение тут не нужно), либо действие тривиально и сводится только к констатации факта ошибки (VERIFY, TRACE), либо при использовании контрактов — самоубийству либо ничего не деланью, просто потому, что сделать ничего нельзя.

В общем, можете привести пример кроме File (его мы уже взвесили и вроде как признали его непригодным для демонстрации бананьев), когда САМ объект гварда (а то, что мы тут обсуждаем, именно это — других примеров я пока не вижу) не может сделать все нужные действия по обработке ошибки освобождения охраняемого ресурса? Я такой пример знаю, правда, я знаю, как его надо решать — без использования исключений вообще.


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


EP>Первое сообщение топика читал? А документацию?

EP>А про двух-фазную деструкцию
Автор: Evgeny.Panasyuk
Дата: 01.10.12
?


Смотрел, и читал. Примеров сценариев использования — не увидел.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[10]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Evgeny.Panasyuk Россия  
Дата: 02.10.12 22:01
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>Хотелось бы понять, зачем. Если у Something нет других методов, кроме deferred, тогда объект не нужен — это функция.


вообще я подразумевал что есть другие методы.
Но, если бы даже не было, и была бы одна только функция deferred — которая должна вызываться в конце scope, если нет исключения, то это типичный scope(success) из языка D (https://github.com/panaseleus/stack_unwinding#d-style-scope-guardsactions — тут и ссылки и примеры), а никак не обычная функция.
Без наличия таких deferred действий приходится использовать костыли в виде ScopeGuard'ких commit/release:
* http://www.boost.org/doc/libs/1_51_0/libs/scope_exit/doc/html/index.html
* http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Scope_Guard
* http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758

AS>Если есть — тогда принимать решение по контракту того, что должно происходить в деструкторе объекта при ошибке освобождения ресурса должен либо внешний код на этом же уровне (т.е. исключение тут не нужно), либо действие тривиально и сводится только к констатации факта ошибки (VERIFY, TRACE), либо при использовании контрактов — самоубийству либо ничего не деланью, просто потому, что сделать ничего нельзя.


При чём тут деструктор и ресурсы?
{
    Something a,b;
    // ...
    b.deferred(); // may throw
    a.deferred(); // may throw
}

Сейчас ведь оно как раз не в деструкторе делается

AS>В общем, можете привести пример кроме File (его мы уже взвесили и вроде как признали его непригодным для демонстрации бананьев),


Я не признавал его непригодным — пример как раз таки нормальный. Я хотел отойти от конкретного примера к обобщённому паттерну отложенных действий.
Дискуссия начала скатываться к обсуждению нужны ли вообще File'у исключения:

AS>Как ни странно, но мой опыт говорит, что при работе с объектом уровня File исключение не должно произойти нигде

Продолжать разговор в этом ключе мне не интересно.

AS>когда САМ объект гварда (а то, что мы тут обсуждаем, именно это — других примеров я пока не вижу) не может сделать все нужные действия по обработке ошибки освобождения охраняемого ресурса? Я такой пример знаю, правда, я знаю, как его надо решать — без использования исключений вообще.


А это не освобождение ресурсов фейлится, фейлится отложенное действие. Само освобождение ресурсов крайне редко может фейлится.
Я вроде об этом уже говорил
Автор: Evgeny.Panasyuk
Дата: 01.10.12
:

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. Если отложенное действие фэйлится — пользователь должен узнать об этом. Проглатывание исключений (потеря информации) — не является хорошим решением.

Даже если фейлится отложенное действие, ресурсы всё равно освобожадются.

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

EP>>Первое сообщение топика читал? А документацию?
EP>>А про двух-фазную деструкцию
Автор: Evgeny.Panasyuk
Дата: 01.10.12
?

AS>Смотрел, и читал. Примеров сценариев использования — не увидел.

Что по этому поводу скажешь?

AS>классическую проблему — что делать, если при выкидывании исключения в деструкторе еще исключение

Например в свете этих двух примеров:
1. https://github.com/panaseleus/stack_unwinding/blob/master/examples/scope_actions.cpp
2. https://github.com/panaseleus/stack_unwinding/blob/master/examples/boost_scopes.cpp
int main(int,char *[])
{
    try
    {
        cout << "Case #1: stack unwinding" << endl;
        BOOST_SCOPE_EXIT(void) { cout << "exit" << endl; } BOOST_SCOPE_EXIT_END
        BOOST_SCOPE_FAILURE(void) { cout << "failure" << endl; } BOOST_SCOPE_FAILURE_END
        BOOST_SCOPE_SUCCESS(void) { cout << "success" << endl; } BOOST_SCOPE_SUCCESS_END
        throw 1;
    } catch(int){}
    {
        cout << "Case #2: normal exit" << endl;
        BOOST_SCOPE_EXIT(void) { cout << "exit" << endl; } BOOST_SCOPE_EXIT_END
        BOOST_SCOPE_FAILURE(void) { cout << "failure" << endl; } BOOST_SCOPE_FAILURE_END
        BOOST_SCOPE_SUCCESS(void) { cout << "success" << endl; } BOOST_SCOPE_SUCCESS_END
    }
    return 0;
}

ExpectedStdoutTest test_cout
(
        "Case #1: stack unwinding\n"
        "failure\n"
        "exit\n"
        "Case #2: normal exit\n"
        "success\n"
        "exit\n"
);
Re[8]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Erop Россия  
Дата: 03.10.12 02:18
Оценка: 12 (1) +1
Здравствуйте, Vamp, Вы писали:

V>>>Ну укажи, какие именно ошибки из возвращаемых fflush могут возникнуть внезапно.

EP>>внезапно закончилось место в разделе
V>Ага. И что ты можешь сделать в этом случае? Что твой гипотетический catch сделает?

Что-нибудь потрёшь?..

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

Тоже самое. Смотри.
try {
    writeDocument();
catch( exception& e ) {
    // обработчик
}
какая разница, на каком конкретно этапе обломилась запись внутри writeDocument()?

V>Может. И деструктор тут не поможет. Отсюда еще раз — деструктор в принципе не должен выполнять действия, могущие вернуть код ошибки. Потому что есть is_unwinding, что нет — восстановиться в этом случае нельзя.


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

з. ы.
Я сам по себе сторонник медленного газа, в смысле противники исключений в деструкторах. IMHO, с ними сложнее код поддерживать, но ни о какой принципиальной их невозможности или бессмысленности речи не идёт вроде...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[9]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Erop Россия  
Дата: 03.10.12 02:20
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>пусть будет не файл, пусть будет что-то, с отложенным действием. Семантика:

EP>
EP>{
EP>    Something a,b;
EP>    // ...
EP>    b.deferred(); // may throw
EP>    a.deferred(); // may throw
EP>}
EP>

EP>Почему бы не использовать предложенный метод, и вместо этого писать:
EP>
EP>{
EP>    Something a,b;
EP>    // ...
EP>}
EP>

EP>?

Потому, что это намного менее очевидно для программиста поддерживающего код...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[10]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Evgeny.Panasyuk Россия  
Дата: 03.10.12 11:08
Оценка:
Здравствуйте, Erop, Вы писали:

EP>>пусть будет не файл, пусть будет что-то, с отложенным действием. Семантика:

EP>>
EP>>{
EP>>    Something a,b;
EP>>    // ...
EP>>    b.deferred(); // may throw
EP>>    a.deferred(); // may throw
EP>>}
EP>>

EP>>Почему бы не использовать предложенный метод, и вместо этого писать:
EP>>
EP>>{
EP>>    Something a,b;
EP>>    // ...
EP>>}
EP>>

EP>>?
E>Потому, что это намного менее очевидно для программиста поддерживающего код...

Согласен, такой момент есть, и он немаловажен.
Все кто будет касаться такого кода должны быть осведомлены об отложенных действиях и кидающихся деструкторах. Иначе у них может даже не возникнуть мысли посмотреть в документацию.
Желательно рядом с каждым созданием этого "Something", ставить ссылку в комментарий.
Либо, использовать более явную схему:
{
    Something a,b;
    BOOST_SCOPE_SUCCESS(void) { b.deferred(); // may throw } BOOST_SCOPE_SUCCESS_END
    BOOST_SCOPE_SUCCESS(void) { a.deferred(); // may throw } BOOST_SCOPE_SUCCESS_END
    // ...
}

Важным отличием от ручного расстановки вызовов .deferred() является то, что этот код вызывается на любом выходе из scope (если не было исключения..). то есть в "// ...", могут быть multiple return's, break's, etc..
Re[11]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Evgeny.Panasyuk Россия  
Дата: 03.10.12 11:09
Оценка:
EP>
EP>{
EP>    Something a,b;
EP>    BOOST_SCOPE_SUCCESS(void) { b.deferred(); /* may throw */ } BOOST_SCOPE_SUCCESS_END
EP>    BOOST_SCOPE_SUCCESS(void) { a.deferred(); /* may throw */ } BOOST_SCOPE_SUCCESS_END
EP>    // ...
EP>}
EP>
Re[11]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Erop Россия  
Дата: 03.10.12 11:43
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Важным отличием от ручного расстановки вызовов .deferred() является то, что этот код вызывается на любом выходе из scope (если не было исключения..). то есть в "// ...", могут быть multiple return's, break's, etc..


Ну я в такой ситаации стронник упрощения кода, или, на крайняк, использования С'шных goto-based подходов, а не дальнейшего запутывания кода.
Но на вкус и цвет, как известно...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[12]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Evgeny.Panasyuk Россия  
Дата: 03.10.12 12:14
Оценка:
Здравствуйте, Erop, Вы писали:

EP>>Важным отличием от ручного расстановки вызовов .deferred() является то, что этот код вызывается на любом выходе из scope (если не было исключения..). то есть в "// ...", могут быть multiple return's, break's, etc..

E>Ну я в такой ситаации стронник упрощения кода, или, на крайняк, использования С'шных goto-based подходов, а не дальнейшего запутывания кода.
E>Но на вкус и цвет, как известно...

Безусловно, код большого размера, с множественными возвратами, break и т.п., — редко когда оправдан.
Но если не рассматривать моральную сторону вопроса — IMO лучше с scope(exit), scope(failure) и с scope(success) — потому что семантика действия зашита в код. При ручном .deferred() "другой" программист может его не заметить и поставить где-нибудь return.
Кстати, вот слайды Александреску: http://ecn.channel9.msdn.com/events/LangNEXT2012/AndreiLangNext.pdf
Re[9]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Vamp Россия  
Дата: 03.10.12 12:54
Оценка:
E>Что-нибудь потрёшь?..
Потереть ты, конечно, можешь — можно хоть файлвую систему пересоздать — только объект-то тю-тю! Нету его больше, так что повторить операцию никак не получится. В этом и есть (я все пытаюсь донести эту мысль) принципиальное отличие исключений в деструкторах от исключений в других местах. Потому что,
а. Исключение в конструкторе — объект не создан, действие не начато, можно попробовать начать еще раз или вообще забить. Программа в консистном состоянии.
б. Исключение по ходу работы — объект существует, контекст существует, операция сорвалась — можно повтороить операцию еще раз.
в. Исключение в деструкторе — контекст утрачен безвозвратно! Никакое восстановление невозможно принципиально.

E>какая разница, на каком конкретно этапе обломилась запись внутри writeDocument()?

См. выше.

E>Почему нельзя? Часто есть боле высокий уровень абстракции, на котором можно...

E>Ну, например, запись в файл не удалась с какой-угодно ошибкой, можно показать пользователю диагностику, и предоставить возможность попробовать записать в файл ещё раз
Да — если обломился обычный write. (Тут, к слову, я абсолютно согласен с Андреем — в объектах, инкапсулирующих логику работы с файлами, исключениям на самом деле вообще не место, за, пожалуй, единственным исключение — попыткой записи/чтения в неоткрытый файл — но для дискуссии оставим). Write обломился, но можно повторить. А деструктор возможности повторить НЕ ДАЕТ!
Да здравствует мыло душистое и веревка пушистая.
Re[10]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Evgeny.Panasyuk Россия  
Дата: 03.10.12 13:33
Оценка:
Здравствуйте, Vamp, Вы писали:

E>>Что-нибудь потрёшь?..

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

Если у тебя действительно код:
void f()
{
    File a("some");
    try
    {
        a.write("...");
    }
    catch(/*something*/)
    {
        /*do something*/
    }
    // ...
    try
    {
        a.flush()
    }
    catch(/*something*/)
    {
        // do something
    }
}

То действительно, исключения в деструкторах тебе не нужны. Но, по всей видимости, при таком коде они вообще не нужны, ну кроме конструкторов
А вот если же код вида:
void f()
{
    File a("some");
    a.write("...");
    // ...
    a.flush()
}

То какая разница вызывающему коду, откуда именно прилетело исключение — из явного flush, или из деструктора?

V>в. Исключение в деструкторе — контекст утрачен безвозвратно! Никакое восстановление невозможно принципиально.


Точно также как и утрачен, в случае когда ОБЫЧНОЕ plain-vanilla исключение ловится уровнями выше
А это, ещё раз замечу, default use-case для исключений.

По поводу утраченного контекста, и невозможности "повторить" вызов деструктора, процитируюДейва Абрахамса:

I have serious problems with your suggestion that re-try-ability is fundamental to what can or should report an error, but leaving that argument aside for the time being, consider this: the exception can always carry any information needed to re-try these side-effects.
...
I’ll say the same thing to you that I said to Herb: the ability to usefully re-try an operation has no obvious bearing on whether it’s a good idea to report that the operation failed. In fact, I’ll go further: if the best response to a particular problem is likely to be “try that again,” an exception is probably not the most appropriate way to report the situation, because it’ll usually need to be re-tried very close to the place where it occurred.
...
Here’s another way of looking at it: destructors are the only way we have in C++ of attaching side-effects to block exit (c.f. finally in other languages), and, yes, to the destruction of other objects, neither one of which is inherently a bad thing to do. Side-effects can fail to complete, and when they do, there are reasons to want to know about it.

Re[10]: Legalize throwing destructors! D's scope(failure) and scope(success) in
От: Erop Россия  
Дата: 04.10.12 12:49
Оценка: +1
Здравствуйте, Vamp, Вы писали:

V>в. Исключение в деструкторе — контекст утрачен безвозвратно! Никакое восстановление невозможно принципиально.

Может быть контекст более высокого уровня...

Вообще, какой-то странный аргумент. Вот возьмём, например, файл. Положим файл вернул из Write, что диск поглупел и больше ничего запомнить не может. Что мы можем исправить такое, для чего нам нужен объякт файла?

V>См. выше.

Вот постарайся таки объяснить какая разница, Write провалится или Close...

E>>V>Да — если обломился обычный write. (Тут, к слову, я абсолютно согласен с Андреем — в объектах, инкапсулирующих логику работы с файлами, исключениям на самом деле вообще не место, за, пожалуй, единственным исключение — попыткой записи/чтения в неоткрытый файл — но для дискуссии оставим). Write обломился, но можно повторить. А деструктор возможности повторить НЕ ДАЕТ!


Чего повторить? И зачем? А вдруг он что-то частично записал. а что-то нет?
Ну и вообще, для многих задач запись документа в файл выглядит, как атомарная операция, и если она провалилась, то логичнее попробовать в другой файл сохранить, например, а не пытаться повторить попытку записи того же куска файла,..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: Evgeny.Panasyuk Россия  
Дата: 11.01.14 11:19
Оценка: 20 (2)
EP>stack_unwinding это маленькая header-only библиотека, которая реализует примитив(class unwinding_indicator) позволяющий определить, был ли вызван деструктор объекта из-за раскрутки стэка или "нормальным" образом

Интегрирована в Facebook.Folly:

Added SCOPE_FAIL and SCOPE_SUCCESS macros in non-portable C++.

Summary:
Added SCOPE_FAIL and SCOPE_SUCCESS macros in non-portable C++. The macros are similar to D's scope(failure) and scope(success).
Currently the supported platforms are GCC and MSVC. For all others, std::uncaught_exception() is used, which will fail if the macros are used in a destructor called during stack unwinding.
@override-unit-failures

Test Plan:
1. Added new unit test to ScopeGuardTest.cpp.
2. Ran fbconfig -r folly && fbmake dbg
3. Ran _build/dbg/folly/test/scope_guard_test to make sure my unit test was running and passing.

Reviewed By: andrei.alexandrescu@fb.com

FB internal diff: D1033621

Re[2]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: niXman Ниоткуда https://github.com/niXman
Дата: 11.01.14 15:59
Оценка:

Daniel Marinescu

а это кто? или это не твой коммит?

зы
в ФБ только румыны работают? Оо
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: Legalize throwing destructors! D's scope(failure) and scope(success) in C
От: Evgeny.Panasyuk Россия  
Дата: 11.01.14 16:07
Оценка: :)
Здравствуйте, niXman, Вы писали:

X>

Daniel Marinescu

X>а это кто?

Не знаю.

X>или это не твой коммит?


Не, не мой. Я вообще об этом случайно узнал (пришёл e-mail от человека который использует).
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: Evgeny.Panasyuk Россия  
Дата: 11.04.14 12:24
Оценка:
EP>На данный момент, библиотека реализована поверх платформо-зависимой реализации функции uncaught_exception_count.
EP>uncaught_exception_count — это функция подобная std::uncaught_exception из стандартной библиотеки, но вместо булевского результата возвращает unsigned int, показывающий текущее количество uncaught exceptions

Ссылка по теме — http://www.reddit.com/r/programming/comments/1rp8es/c_secrets_ds_scope_statement_in_c/ (или почему недостаточно std::uncaught_exception).
Re: Legalize throwing destructors! D's scope(failure) and scope(success) in C++
От: Evgeny.Panasyuk Россия  
Дата: 24.04.15 20:02
Оценка: 28 (2) +1
EP>На данный момент, библиотека реализована поверх платформо-зависимой реализации функции uncaught_exception_count.
EP>uncaught_exception_count — это функция подобная std::uncaught_exception из стандартной библиотеки, но вместо булевского результата возвращает unsigned int, показывающий текущее количество uncaught exceptions

N4152 — proposal по добавлению int std::uncaught_exceptions() в стандарт.
Уже включен в текущий iso draft n4296:

15.5.3 The std::uncaught_exceptions() function [except.uncaught]
1 An exception is considered uncaught after completing the initialization of the exception object (15.1) until completing the activation of a handler for the exception (15.3). This includes stack unwinding. If the exception is rethrown (15.1), it is considered uncaught from the point of rethrow until the rethrown exception is caught again. The function std::uncaught_exceptions() (18.8.4) returns the number of uncaught exceptions.

Re[2]: Legalize throwing destructors! D's scope(failure) and
От: Skorodum Россия  
Дата: 25.04.15 21:06
Оценка: 27 (1)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Интегрирована в Facebook.Folly:

EP>

...
EP>Reviewed By: andrei.alexandrescu@fb.com
...


Ну он же тебе за это даже публично спасибо говорил
Автор: Skorodum
Дата: 06.06.14
Даже в слайдах
Автор: Skorodum
Дата: 22.12.14
вроде ссылка на тебя есть. Вообще этой идее (scope-failure/scope-success) было посвящено часа полтора его семинара.

Upd: В слайдах: 10-Declarative-Control-Flow.handouts.pdf, страница 27. 06-ErrorHandling.handouts.pdf тоже об этом.
Отредактировано 25.04.2015 21:13 Skorodum . Предыдущая версия .
Re[3]: Legalize throwing destructors! D's scope(failure) and
От: Evgeny.Panasyuk Россия  
Дата: 26.04.15 19:10
Оценка:
Здравствуйте, Skorodum, Вы писали:

S>Ну он же тебе за это даже публично спасибо говорил
Автор: Skorodum
Дата: 06.06.14
Даже в слайдах
Автор: Skorodum
Дата: 22.12.14
вроде ссылка на тебя есть. Вообще этой идее (scope-failure/scope-success) было посвящено часа полтора его семинара.

S>Upd: В слайдах: 10-Declarative-Control-Flow.handouts.pdf, страница 27. 06-ErrorHandling.handouts.pdf тоже об этом.

Да, большое спасибо — я смотрел это выступление как раз по твоей наводке
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.