delete в Деструкторе
От: buka123  
Дата: 19.08.09 05:14
Оценка: 4 (2)
~machine()
{
for (list<state*>::iterator it=states.begin();it!=states.end();it++)
delete (*it);
}
Достаточно ли этого. Или надо окружить try... catch?
Re: delete в Деструкторе
От: catBasilio  
Дата: 19.08.09 06:12
Оценка:
Здравствуйте, buka123, Вы писали:

B>~machine()

B>{
B>for (list<state*>::iterator it=states.begin();it!=states.end();it++)
B> delete (*it);
B>}
B>Достаточно ли этого. Или надо окружить try... catch?

я бы вообще shared_ptr использовал


class machines
{
 
...
private:
  std::list<std::tr1::shared_ptr<state> > states;
}


а то, в каком-нибудь месте забудешь объекты поудалять и мемори лик будет.
UNIX way — это когда тебе вместо туалетной бумаги дают топор, рубанок и карту близлежащего леса
Re: delete в Деструкторе
От: rg45 СССР  
Дата: 19.08.09 06:18
Оценка:
Здравствуйте, buka123, Вы писали:

B>~machine()

B>{
B>for (list<state*>::iterator it=states.begin();it!=states.end();it++)
B> delete (*it);
B>}
B>Достаточно ли этого. Или надо окружить try... catch?

Очень интересный вопрос. В стандарте по этому поводу существует такая рекомендация:

15.2(3)
The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [Note: If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note]

Александреску и Саттер в своей книге Стандарты программирования на C++
Автор(ы): Герб Саттер, Андрей Александреску

Эта книга поможет новичку стать профессионалом, так как в ней
представлен сконцентрированный лучший опыт программистов на C++,
обобщенный двумя экспертами мирового класса. Начинающий программист
найдет в ней простые и понятные рекомендации для ежедневного
использования, подкрепленные примерами их конкретного применения
на практике.
также говорят о том, что все исключения от внутренних вызовов в деструкторах должны перехватываться. Но вот вопрос: а что делать с внутренними вызовами других деструкторов? Ведь если мы работаем с "правильными" типами, то имеем право расчитавать на то, что эти деструкторы также не бросают исключений. С другой строны, при вызове оператора delete не исключена возможность возникновения исключения за пределами деструктора — если запорчена память, например. Но вот, например, boost::shared_ptr в своем деструкторе не перехватывает исключений, возникающих при освобождении адресуемого объекта. И на мой взгляд было бы странно, если бы он тупо все перехватывал. Так что я присоединяюсь к вопросу, мне тоже интересно послушать мнения на этот счет.

З.Ы. В данном примере лично я бы помещал в список не голые указатели, а умные, например, все тот же boost::shared_ptr. Но в данном случае это так, мелкая придирка, не меняющая сути проблемы.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: delete в Деструкторе
От: Quadri  
Дата: 19.08.09 06:44
Оценка:
Здравствуйте, buka123, Вы писали:

B>~machine()

B>{
B>for (list<state*>::iterator it=states.begin();it!=states.end();it++)
B> delete (*it);
B>}
B>Достаточно ли этого. Или надо окружить try... catch?

У меня немного другой вопрос: а в каких случаях вообще может возникать исключение в деструктора нормального объекта? На ум приходит лишь запорченная память — но ведь это явная ошибка программирования, которая обязательно устраняется и проблема по идее исчезает?
Re[2]: delete в Деструкторе
От: Chorkov Россия  
Дата: 19.08.09 08:00
Оценка:
Здравствуйте, Quadri, Вы писали:

Q>Здравствуйте, buka123, Вы писали:



B>>~machine()
B>>{
B>>for (list<state*>::iterator it=states.begin();it!=states.end();it++)
B>>    delete (*it);
B>>}


B>>Достаточно ли этого. Или надо окружить try... catch?


Q>У меня немного другой вопрос: а в каких случаях вообще может возникать исключение в деструктора нормального объекта? На ум приходит лишь запорченная память — но ведь это явная ошибка программирования, которая обязательно устраняется и проблема по идее исчезает?



К сожалению, в стандарт дает всего лишь рекомендацию, а книги Александреску не имеют юридической силы.
Поэтому рассчитывать на отсутствие исключения в деструкторе — нельзя. (Если только класс разрушаемого объекта заранее не известен.) Исключения в деструкторах иногда встречаются в разного рода обертках.

Например, "Очень Злой Сам-себе Буратино " может закрывать транзакцию в деструкторе объекта и бросать исключение, если она не прошла.
При выходе из критической секции, защищающей работы с набором глобальных переменных, "Некто" может вписать ассерт, проверяющей согласованность значений глобальных переменных, который со временем прорефакторится до "проверка инварианта + throw" (тоже транзакция, но хорошо замаскированная).
Очевидно, это ошибки проектирования, но никто не даст гарантии что что работать придется только с правильно спроектированным кодом.
Re[2]: delete в Деструкторе
От: Alexander G Украина  
Дата: 19.08.09 08:02
Оценка: 6 (1) +5
Здравствуйте, rg45, Вы писали:

R>Но вот вопрос: а что делать с внутренними вызовами других деструкторов? Ведь если мы работаем с "правильными" типами, то имеем право расчитавать на то, что эти деструкторы также не бросают исключений. С другой строны, при вызове оператора delete не исключена возможность возникновения исключения за пределами деструктора — если запорчена память, например. Но вот, например, boost::shared_ptr в своем деструкторе не перехватывает исключений, возникающих при освобождении адресуемого объекта. И на мой взгляд было бы странно, если бы он тупо все перехватывал. Так что я присоединяюсь к вопросу, мне тоже интересно послушать мнения на этот счет.


Порча памяти произошла уже в результате UB. Да и не ловить такие случаи нужно, а напротив, дать SEH-исключению дойти до обработки ОС, чтобы подключить дебаггер или создать креш-дамп и остановить работу.

В другой книге Саттера
Автор(ы): Герб Саттер

В данном издании объединены две широко известные профессионалам в области
программирования на C++ книги Герба Саттера Exceptional C++
и More Exceptional C++, входящие в серию книг C++ In-Depth,
редактором которой является Бьерн Страуструп, создатель языка C++.
Материал этой книги составляют переработанные задачи серии Guru of the Week,
рассчитанные на читателя с достаточно глубоким знанием C++.
Однако книга будет полезна каждому, кто хочет углубить свои знания в этой
области.
 было показано, что если деструкторы могут кидать исключения, то даже со встроенными массивами будут проблемы. Задача 2.9, с 129-131.

Не кидать и не ловить.
Русский военный корабль идёт ко дну!
Re: delete в Деструкторе
От: Alexander G Украина  
Дата: 19.08.09 08:15
Оценка:
Здравствуйте, buka123, Вы писали:

B>Достаточно ли этого. Или надо окружить try... catch?


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

Лучше обеспечить, чтобы state не бросал, тогда можно будет применять смартпоинтеры, ну и вообще, см. выше в теме
Автор: Alexander G
Дата: 19.08.09
ссылку на Саттера. Там как раз этот случай рассмотрен.
Русский военный корабль идёт ко дну!
Re[2]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.08.09 08:19
Оценка:
Здравствуйте, Quadri, Вы писали:

Q>У меня немного другой вопрос: а в каких случаях вообще может возникать исключение в деструктора нормального объекта?


Вызов функции вроде flush в дестукторе с последующим возбуждением исключения в случае ошибки. Лучшим решением будет предоствление специальной функции для тих целей: там где действительно необходимо узнать об ошибке — будет явный вызов + catch. В деструкторе же исключение нужно подавлять, иначе возникает как минимум озвученная проблема. Или вот такая:

Object t; // деструктор генерирует исключение
//...
throw exception();
//...
Re: delete в Деструкторе
От: LaptevVV Россия  
Дата: 19.08.09 08:24
Оценка:
Здравствуйте, buka123, Вы писали:

B>~machine()

B>{
B>for (list<state*>::iterator it=states.begin();it!=states.end();it++)
B> delete (*it);
B>}
B>Достаточно ли этого. Или надо окружить try... catch?
По стандарту операция delete исключения не возбуждает.
А вот state — смотреть надо
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[3]: delete в Деструкторе
От: rg45 СССР  
Дата: 19.08.09 11:05
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

ЮЖ>...В деструкторе же исключение нужно подавлять, иначе возникает как минимум озвученная проблема. Или вот такая:


ЮЖ>
ЮЖ>Object t; // деструктор генерирует исключение
ЮЖ>//...
ЮЖ>throw exception();
ЮЖ>//...
ЮЖ>


Давайте пример слегка доработаем.
#include <boost/shared_ptr.hpp>

class A {};

void main()
{
  A* a = new A();
  boost::shared_ptr<A> p(a); //умный указатель, который не перехватывает исключения в свое деструкторе
  delete a; //наши кривые руки (упрощенная модель)
  throw std::exception(); //бросок исключения и в процессе раскрутки стека еще одно исключение, летящее из деструктора boost::shared_ptr
}

И снова вопрос, который не дает покоя: были ли должны разработчики boost::shared_ptr перехватить в деструкторе этого смарт-поинтера исключениние, возникающее при удалении адресуемого объекта или нет?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: delete в Деструкторе
От: jazzer Россия Skype: enerjazzer
Дата: 19.08.09 11:24
Оценка: 6 (1) +4
Здравствуйте, rg45, Вы писали:

R>Так что я присоединяюсь к вопросу, мне тоже интересно послушать мнения на этот счет.


Мое мнение — надо по умолчанию предполагать, что деструкторы не бросают исключений, и не писать никаких блокирующих оберток.
Бросающий деструктор — это исключительный случай.
И явно говорить, если мы это делаем.
По умолчанию — деструкторы не бросают, это должно быть политикой.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: delete в Деструкторе
От: Alexander G Украина  
Дата: 19.08.09 11:36
Оценка:
Здравствуйте, rg45, Вы писали:

R>И снова вопрос, который не дает покоя: были ли должны разработчики boost::shared_ptr перехватить в деструкторе этого смарт-поинтера исключениние, возникающее при удалении адресуемого объекта или нет?


Нет. Они ж не знают, как его обработать.

А синтетический пример можно выдумать такой, что наоборот, разумно пустить исключение дальше:
{
  scoped_ptr<DelayedFileWrite> file(new DelayedFileWrite(name)); // файл с отложенной записью
  ...
  // запись в деструкторе, при ошибке кидает, так надо знать, что ошибка произошла.
}


Как не следует вставлять в контейнер stl объект с необычными конструтором копирования и оператором присвоения, точно так же не следует пихать объект с кидающим деструктором вообще куда либо.
Русский военный корабль идёт ко дну!
Re[5]: delete в Деструкторе
От: Chorkov Россия  
Дата: 19.08.09 12:02
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, rg45, Вы писали:


R>>И снова вопрос, который не дает покоя: были ли должны разработчики boost::shared_ptr перехватить в деструкторе этого смарт-поинтера исключениние, возникающее при удалении адресуемого объекта или нет?


AG>Нет. Они ж не знают, как его обработать.


AG>А синтетический пример можно выдумать такой, что наоборот, разумно пустить исключение дальше:

AG>
AG>{
AG>  scoped_ptr<DelayedFileWrite> file(new DelayedFileWrite(name)); // файл с отложенной записью
AG>  ...
AG>  // запись в деструкторе, при ошибке кидает, так надо знать, что ошибка произошла.
AG>}
AG>


AG>Как не следует вставлять в контейнер stl объект с необычными конструтором копирования и оператором присвоения, точно так же не следует пихать объект с кидающим деструктором вообще куда либо.


Развивая эту мысль, до логического завершения, объект с кидающим деструктором вообще нельзя использовать.
{
  DelayedFileWrite file1(name1); 
  DelayedFileWrite file2(name2); 
  //...

  // А если я выну флешку, на которую оба эти файла должны были бы быть записаны ?

  // ...
}
Re: delete в Деструкторе
От: Кодт Россия  
Дата: 19.08.09 12:19
Оценка:
Здравствуйте, buka123, Вы писали:

Немножко красоты к коду:
std::for_each(states.begin(), states.end(), boost::checked_delete<state>);
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[3]: delete в Деструкторе
От: Николай Ивченков  
Дата: 19.08.09 12:55
Оценка: 6 (1)
jazzer:

J>Мое мнение — надо по умолчанию предполагать, что деструкторы не бросают исключений, и не писать никаких блокирующих оберток.


Cобственно, стандартная библиотека C++ именно на таком предположении и работает:

17.4.3.6:

1 In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ Standard Library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.

2 In particular, the effects are undefined in the following cases:
[...]
— if any replacement function or handler function or destructor operation throws an exception, unless specifically allowed in the applicable Required behavior paragraph.

Re[4]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.08.09 13:35
Оценка: 9 (1)
Здравствуйте, rg45, Вы писали:
...
R>Давайте пример слегка доработаем.
R>
R>#include <boost/shared_ptr.hpp>

R>class A {};

R>void main()
R>{
R>  A* a = new A();
R>  boost::shared_ptr<A> p(a); //умный указатель, который не перехватывает исключения в свое деструкторе
R>  delete a; //наши кривые руки (упрощенная модель)
R>  throw std::exception(); //бросок исключения и в процессе раскрутки стека еще одно исключение, летящее из деструктора boost::shared_ptr
R>}
R>

Откуда здесь исключение в деструкторе shared_ptr? UB при повтороном удалении...

R>И снова вопрос, который не дает покоя: были ли должны разработчики boost::shared_ptr перехватить в деструкторе этого смарт-поинтера исключениние, возникающее при удалении адресуемого объекта или нет?

Нет, они не знают что с ним делать и неизвестно возможно ли оно вообще. Хотя для boost::shared_ptr просто написали "The behavior of the smart pointer templates is undefined if the destructor or operator delete for objects of type T throw exceptions".

Я вот какой случай имею ввиду:
Вариант 1) имеется небросающая функция 'int api::close(resource&)' которая возвращает OK в случае успеха и код ошибки в противном случае. Обертка (или что-то посложнее) для нее может выглядеть так (для упрощения разрешим повторное закрытие, не приводящее ни к чему):

struct X
{
  api::resource m_resource;

  void close()
  {
    int r = api::close(m_resource);
    if(r != OK)
      throw some_exception(r);
  }

  ~X()
  {
    api::close(m_resource); // nothrow
  }
};


Аналогичный вариант для случая с кидающей close (такой вот api) будет:
struct X
{
  api::resource m_resource;

  void close() {
    api::close(m_resource);
  }

  ~X()
  {
    try {
      this->close();
    } catch(const std::exception&)
    {}
  }
};


В двух этих вариантах поведение программы в некоторых местах зависит от успешности api::close, в некоторых нет. Если не замалчивать исключение в деструкторе, то и первый вариант для сохранения идентичности следует переписать так (сюрприз):

struct X
{
  void close()
  {
    int r = api::close(m_resource);
    if(r != OK)
      throw some_exception(r);
  }

  ~X()
  {
     this->close();    
  }
}


Т.е. одновременные призывы не кидать из деструктора и не замалчивать — звучат несколько странно =) Не кидать, и все. Если действиетельно приходится использовать кидающие операции в деструкторе — то оборачивать, т.к. игнорирование в этом случае является (должно по крайней мере) преднамеренным. Здесь можно кинуть камень в огород библиотекам/фреймворками злоупотребляющими исключениями — не в их пределах ответственности решать, является ли диагностированное состояние исключительной ситуаций для вызывающего.
Re[5]: delete в Деструкторе
От: Alexander G Украина  
Дата: 19.08.09 13:40
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

ЮЖ>Здесь можно кинуть камень в огород библиотекам/фреймворками злоупотребляющими исключениями — не в их пределах ответственности решать, является ли диагностированное состояние исключительной ситуаций для вызывающего.


И boost::interruption_point() ?
Есть возможность запретить их временно, ещё бы о ней вспомнить когда надо.
Русский военный корабль идёт ко дну!
Re[6]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.08.09 14:19
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, Юрий Жмеренецкий, Вы писали:


ЮЖ>>Здесь можно кинуть камень в огород библиотекам/фреймворками злоупотребляющими исключениями — не в их пределах ответственности решать, является ли диагностированное состояние исключительной ситуаций для вызывающего.


AG>И boost::interruption_point() ?

Это не злоупотребление, ты бы еще throw_exception вспомнил =)
Re[7]: delete в Деструкторе
От: Alexander G Украина  
Дата: 19.08.09 14:58
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

ЮЖ>Это не злоупотребление, ты бы еще throw_exception вспомнил =)


Ну как, вот мы в деструкторе ждём, допустим, другой тред, и наш прерывают:
X::~X() {
  m_thd.wait_for();
}


не очевидно, особенно с учётом того, что это исключение редко надо обрабатывать, и оно не std::exception
Русский военный корабль идёт ко дну!
Re: delete в Деструкторе
От: Vamp Россия  
Дата: 19.08.09 15:12
Оценка:
Ни в коем случае не оборачивай. Что ты собираешься делать, если деструктор бросил исключение? В каком состоянии находится объект? Тут уже ничего не исправить все равно, так что пусть программа надежно свалиться — по крайней мере, в трейсе будет четко видно откуда оно пришло и его можно будет пофиксить.
Да здравствует мыло душистое и веревка пушистая.
Re: delete в Деструкторе
От: D14  
Дата: 19.08.09 15:12
Оценка:
Хочу поднять смежный вопрос.
Вызов деструктора при исключении это семантически неявный блок catch. В блоке catch доступен объект текущего исключения, а в деструкторе нет. Если бы его можно было как-то получить, то исключение вызвавшее деструктор и выбрасываемое из деструктора исключение можно было бы cкомбинировать в одно. Например, исключение вызвавшее деструктор завернуть в поле inner(как в C#) перед выбросом исключения. Почему так нельзя делать?

struct A    
{
    A(){}
    ~A()
    {
        if (std::uncaught_exception())
        {
            try
            {
               throw;
            }
            catch (exception& inner)
            {
                throw exception("Bla-bla-bla",inner);
            }                   
      
        }
        else        
        {
            throw exception("Bla-bla-bla");
        }
    }
};
Re[4]: delete в Деструкторе
От: jazzer Россия Skype: enerjazzer
Дата: 19.08.09 15:37
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:

НИ>jazzer:


J>>Мое мнение — надо по умолчанию предполагать, что деструкторы не бросают исключений, и не писать никаких блокирующих оберток.


НИ>Cобственно, стандартная библиотека C++ именно на таком предположении и работает


Именно.
И не только она.
Мне даже не приходит в голову библиотек, которые не так работают.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[8]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.08.09 15:38
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, Юрий Жмеренецкий, Вы писали:


ЮЖ>>Это не злоупотребление, ты бы еще throw_exception вспомнил =)


AG>Ну как

Здесь(interruption_point) хотя бы руль есть для отключения.

AG>вот мы в деструкторе ждём, допустим, другой тред, и наш прерывают:

AG>не очевидно, особенно с учётом того, что это исключение редко надо обрабатывать, и оно не std::exception
Очевидно/не очевидно — это другой аспект, имхо...
Re[5]: delete в Деструкторе
От: Николай Ивченков  
Дата: 19.08.09 17:04
Оценка:
Юрий Жмеренецкий:

ЮЖ>Т.е. одновременные призывы не кидать из деструктора и не замалчивать — звучат несколько странно =)


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

void some_exception_handler()
{
    try
    {
        throw;
    }
    catch (LackOfDiskSpace &)
    {
        for (;;)
        {
            auto user_decision = show_dialog(....);

            if (user_decision == eliminate_error)
                break;
            if (user_decision == ignore_error)
                throw;
            if (user_decision == suspend_task)
            {
                switch_to_other_task();
                wait(); // suspend thread for some time
            }
        }
    }
}

class X
{
public:
    X(...., boost::function<void ()> const &exception_handler) :
        m_exception_handler(exception_handler),
        ....
    {
        ....
    }
    ~X()
    {
        for (;;)
            try
            {
                destruction(); // may throw
                break;
            }
            catch (...)
            {
                try
                {
                    m_exception_handler();
                }
                catch (...)
                {
                    break; // ignore error
                }
            }
    }
    ....
private:
    void destruction();
    boost::function<void ()> m_exception_handler;
    ....
};

void f()
{
    X x(...., some_exception_handler);
}
Re[6]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.08.09 20:34
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:

НИ>Юрий Жмеренецкий:


ЮЖ>>Т.е. одновременные призывы не кидать из деструктора и не замалчивать — звучат несколько странно =)


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

Можно. Только
1) На момент создания объекта стратегия обработки может быть неизвестна.
2) Невозможно "слегка" размотать стек и обработать исключение в другом месте. Если бы обработчик не находился в деструкторе, можно было бы просто выкинуть исключение специального типа.
2.1) Размотка может привести к освобождению некоторых ресурсов и, с некоторой долей вероятности (зависит от задачи естественно), повторный запуск будет "более" успешным, чем при обработке "по месту".
3) К месту обработки скорее всего придется протаскивать контекст, когда как при "обычном" try/catch(там где имеет смысл обрабатывать) он рядом.
И т.д.
Re[7]: delete в Деструкторе
От: Николай Ивченков  
Дата: 20.08.09 00:07
Оценка:
Юрий Жмеренецкий:

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

ЮЖ>Можно. Только
ЮЖ>1) На момент создания объекта стратегия обработки может быть неизвестна.

Обработчик может быть передан из вызывающих функций через их параметры по цепочке, либо посредством thread local storage.

ЮЖ>2) Невозможно "слегка" размотать стек и обработать исключение в другом месте. Если бы обработчик не находился в деструкторе, можно было бы просто выкинуть исключение специального типа.


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

ЮЖ>3) К месту обработки скорее всего придется протаскивать контекст, когда как при "обычном" try/catch(там где имеет смысл обрабатывать) он рядом.


Я неспроста использовал boost::function. В функторе-обработчике можно сохранить всю нужную для обработки информацию.
Re[8]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 20.08.09 06:48
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:

НИ>Юрий Жмеренецкий:


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

ЮЖ>>Можно. Только
ЮЖ>>1) На момент создания объекта стратегия обработки может быть неизвестна.

НИ>Обработчик может быть передан из вызывающих функций через их параметры по цепочке, либо посредством thread local storage.


Может быть передан == стратегия известна. Неизвестной она может быть в какой-нибудь factory на момент создания объекта. Это только отголоски того факта, что стратегия обработки ошибок рассматривается как часть состояния объекта. Являются ли два объекта с разными стратегиями, но с одинаковым остальным состоянием экивалентными? Можно ли менять стратегии обработки после создания? С TLS еще больше вопросов.

ЮЖ>>2) Невозможно "слегка" размотать стек и обработать исключение в другом месте. Если бы обработчик не находился в деструкторе, можно было бы просто выкинуть исключение специального типа.


НИ>Обработка без раскрутки стека имеет свои преимущества: после устранения причин сбоя выполнение программы может быть продолжено прямо с того места, где возникла ошибка. В случае выбрасывания исключения наверх часть контекста теряется.


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

ЮЖ>>3) К месту обработки скорее всего придется протаскивать контекст, когда как при "обычном" try/catch(там где имеет смысл обрабатывать) он рядом.


НИ>Я неспроста использовал boost::function. В функторе-обработчике можно сохранить всю нужную для обработки информацию.


Т.е. с тем что контекст все-таки придется протаскивать, ты согласен? Заодно придется определить семантику хранения членов контекста. Это особенно актуально, если стратегии можно изменять/сохранять.

-----------

Все что ты описываешь, можно с легкостью использовать при выносе в отдельный метод с формированием явной инфраструктуры. Пока же я воспринимаю это только как самоцель — обработать исключение в деструкторе, попутно решив возникающие при этом проблемы, и получить усложнение на ровном месте. Непосредственно обработка меня не интересует — это полностью проблема клиента. Куда больше бенефитов я вижу от возможности явно указывать какие ошибки нужно транслировать в исключения. И здесь необходимость замалчивания исключений в деструкторе связана _только_ с деталью реализации нижележащего слоя.
Re[9]: delete в Деструкторе
От: Николай Ивченков  
Дата: 20.08.09 11:48
Оценка:
Юрий Жмеренецкий:

ЮЖ>Может быть передан == стратегия известна.


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

ЮЖ>Являются ли два объекта с разными стратегиями, но с одинаковым остальным состоянием экивалентными?


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

ЮЖ>С TLS еще больше вопросов.


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

ЮЖ>>>2) Невозможно "слегка" размотать стек и обработать исключение в другом месте. Если бы обработчик не находился в деструкторе, можно было бы просто выкинуть исключение специального типа.


НИ>>Обработка без раскрутки стека имеет свои преимущества: после устранения причин сбоя выполнение программы может быть продолжено прямо с того места, где возникла ошибка. В случае выбрасывания исключения наверх часть контекста теряется.


ЮЖ>Преимущество — это возможность использовать оба варианта. Ты же пытаешься представить единственно возможный способ априори правильным.


В случае принятия решения о размещении небессбойного кода в деструкторе выбор невелик. Хотя веских причин для принятия такого решения я не знаю

ЮЖ>>>3) К месту обработки скорее всего придется протаскивать контекст, когда как при "обычном" try/catch(там где имеет смысл обрабатывать) он рядом.


НИ>>Я неспроста использовал boost::function. В функторе-обработчике можно сохранить всю нужную для обработки информацию.


ЮЖ>Т.е. с тем что контекст все-таки придется протаскивать, ты согласен?


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

ЮЖ>Заодно придется определить семантику хранения членов контекста. Это особенно актуально, если стратегии можно изменять/сохранять.


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

ЮЖ>Все что ты описываешь, можно с легкостью использовать при выносе в отдельный метод с формированием явной инфраструктуры.


Скорее всего, можно, но на 100% я не уверен (если я правильно понял, имеется в виду удаление всего небессбойного кода из деструктора и размещение его в специальном методе).
Re[10]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 20.08.09 15:03
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:

НИ>Юрий Жмеренецкий:


ЮЖ>>Может быть передан == стратегия известна.


НИ>Общая стратегия — да, известна. Конкретные же действия, которые нужно выполнить при обработке исключительной ситуации, могут задаваться вызывающим кодом (т.е. они не будет жёстко зашиты в деструктор).


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

/*virtual*/ void X::set_error_handler(function<void()>)


ЮЖ>>С TLS еще больше вопросов.


НИ>В случае с TLS стратегию обработки ошибок можно вообще не хранить в объекте, а доставать прямо из хранилища, выделенного для данного потока. Помещать стратегию в конец хранилища и удалять её оттуда будет одна из вызывающих функций.


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

НИ>В случае принятия решения о размещении небессбойного кода в деструкторе выбор невелик.

НИ>Хотя веских причин для принятия такого решения я не знаю

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

Приблизительно вот такой код можно использовать в обоих озвученных стратегиях:

void X::close()
{
  assert(!closed);
  if(!api::close(...))
    throw some_exception();
  assert(closed);
}

X::~X()
{
  if(!closed)
    api::close(...);
}


Здесь возможная проблема — забытый вызов close для первой стратегии. Но, во-первых "отсутствие такой обработки рассматривается как ошибка в программе", во-вторых — при наличии сформулированной задачи, это не проблема(имхо).

Обработка ошибок в деструкторе здесь не нужна по определению (двух вышеописанных стратегий). Если api::close кидает исключения — их просто необходимо игнорировать. Так же как и игнорировать любые возвращаемые результаты.

ЮЖ>>Заодно придется определить семантику хранения членов контекста. Это особенно актуально, если стратегии можно изменять/сохранять.


НИ>При использовании TLS достаточно обычных ссылок — обработка исключения будет осуществлена в том потоке, который задал стратегию обработки.


Ссылок может и достаточно, но семантику определить все же придется, иначе ссылки могут быть мертвыми.

ЮЖ>>Все что ты описываешь, можно с легкостью использовать при выносе в отдельный метод с формированием явной инфраструктуры.


НИ>Скорее всего, можно, но на 100% я не уверен (если я правильно понял, имеется в виду удаление всего небессбойного кода из деструктора и размещение его в специальном методе).


В Boost.IOStreams есть концепции closable/flushable — это почти то, о чем я говорю.
Re[11]: delete в Деструкторе
От: Николай Ивченков  
Дата: 20.08.09 16:50
Оценка: +1
Юрий Жмеренецкий:

ЮЖ>И в результате получится вручную поддерживаемый стек активных обработчиков.


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

// обычная обработка исключений
try
{
    ....
}
catch (ExceptionType &)
{
    ....
}

// задаём последний обработчик (выполняемый на месте возникновения ошибки) как аргумент конструктора,
// деструктор удалит этот обработчик из списка локальных обработчиков потока
LocalExceptionHandler local_exception_handler(handler_function);


ЮЖ>Я в первую очередь говорю о случаях, когда можно исключить необходимость обработки сбойных случаев в деструкторе. Звучит противоречиво, объясню:

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

Мысль ясна.

ЮЖ>Приблизительно вот такой код можно использовать в обоих озвученных стратегиях:


ЮЖ>
void X::close()
{
  assert(!closed);
  if(!api::close(...))
    throw some_exception();
  assert(closed);
}

X::~X()
{
  if(!closed)
    api::close(...);
}


ЮЖ>Здесь возможная проблема — забытый вызов close для первой стратегии. Но, во-первых "отсутствие такой обработки рассматривается как ошибка в программе", во-вторых — при наличии сформулированной задачи, это не проблема(имхо).


IMHO, это всё-таки проблема, но решаемая. Забыть вызвать close всё-таки можно, и во время штатной работы программы это никак не проявится. Поэтому, вероятно, выбранную стратегию следует указывать при создании объекта, а в деструкторе проверять, был ли вызван close. Если вызова close не было, но явно такое намерение не было зафиксировано, то деструктор должен выкинуть assertion failed или сообщить о внутренней ошибке в программе каким-то иным способом. Т.е. надо что-то вроде:

enum ClosingPolicy { close_explicitly, close_soever };

....

X::X(ClosingPolicy closing_policy = close_explicitly) :
  m_closing_policy(closing_policy) {}

void X::close()
{
  if (closed)
    return;
  if (!api::close(....))
    throw some_exception();
  assert(closed);
}

X::~X()
{
  if (!closed)
  {
    assert(m_closing_policy != close_explicitly)
    api::close(....);
  }
}
Re[12]: delete в Деструкторе
От: Юрий Жмеренецкий ICQ 380412032
Дата: 20.08.09 19:08
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:

НИ>... Забыть вызвать close всё-таки можно, и во время штатной работы программы это никак не проявится. Поэтому, вероятно, выбранную стратегию следует указывать при создании объекта, а в деструкторе проверять, был ли вызван close. Если вызова close не было, но явно такое намерение не было зафиксировано, то деструктор должен выкинуть assertion failed или сообщить о внутренней ошибке в программе каким-то иным способом. Т.е. надо что-то вроде:


НИ>
enum ClosingPolicy { close_explicitly, close_soever };

НИ>....

НИ>X::X(ClosingPolicy closing_policy = close_explicitly) :
НИ>  m_closing_policy(closing_policy) {}

НИ>void X::close()
НИ>{
НИ>  if (closed)
НИ>    return;
НИ>  if (!api::close(....))
НИ>    throw some_exception();
НИ>  assert(closed);
НИ>}

НИ>X::~X()
НИ>{
НИ>  if (!closed)
НИ>  {
НИ>    assert(m_closing_policy != close_explicitly)
НИ>    api::close(....);
НИ>  }
НИ>}

Хороший вариант. Не соглашусь только с заменой assert'a в X::close на 'if (closed)return'. Повторный вызов — такая же логическая ошибка как и отсутствие вызова. Это не оговоривалось, но assert там присутствовал именно из этих соображений.

При некоторых условиях можно пойти дальше — если api::close возвращает несколько категорий ошибок ('close' как аналогия здесь уже мало подходит, хотя...), то в конструкторе можно указать, какие коды возврата необходимо транслировать в исключения. Если указана генерация хотя бы одного исключения (если дать возможность указывать несколько типов), то проверка в деструкторе на явный вызов close должна выполняться, иначе (игнорирование всех ошибок) ошибочным должен считаться явный вызов close, т.к. в этом случае вызов не приведет к исключению, тогда как основной его смысл — именно детектирование исключений.
Re[2]: delete в Деструкторе
От: Константин Россия  
Дата: 20.08.09 20:49
Оценка: 1 (1)
Здравствуйте, rg45, Вы писали:

R>Так что я присоединяюсь к вопросу, мне тоже интересно послушать мнения на этот счет.

R>З.Ы. В данном примере лично я бы помещал в список не голые указатели, а умные, например, все тот же boost::shared_ptr. Но в данном случае это так, мелкая придирка, не меняющая сути проблемы.

По-моему посткриптум отвечает на поставленный вопрос. Код с умными указателями и деструктором по-умолчанию практически эквивалентен коду, где исключение не ловится.
Re[12]: delete в Деструкторе
От: Alexander G Украина  
Дата: 24.08.09 12:33
Оценка:
Здравствуйте, Николай Ивченков, Вы писали:


void X::close()
{
  if (closed)
    return;
  if (!api::close(....))
    throw some_exception();
  assert(closed);
}

X::~X()
{
 if (!closed)
 {
    assert(m_closing_policy != close_explicitly)
    api::close(....);
  }
}



Как это будет использовано практически?

{
  X x;
  x.write("hello"); // бросает
  x.close(); // пропускаем
} // сработал assert. как фиксим?
Русский военный корабль идёт ко дну!
Re[2]: delete в Деструкторе
От: Аноним  
Дата: 24.08.09 18:57
Оценка:
Здравствуйте, D14, Вы писали:

D14>
D14>struct A    
D14>{
D14>    A(){}
D14>    ~A()
D14>    {
D14>        if (std::uncaught_exception())
D14>        {
D14>            try
D14>            {
D14>               throw;
D14>            }
D14>            catch (exception& inner)
D14>            {
D14>                throw exception("Bla-bla-bla",inner);
D14>            }                   
      
D14>        }
D14>        else        
D14>        {
D14>            throw exception("Bla-bla-bla");
D14>        }
D14>    }
D14>};
D14>



try { throw } catch (...) {} в деструкторе не работает.
Re[13]: delete в Деструкторе
От: Николай Ивченков  
Дата: 24.08.09 20:58
Оценка:
Alexander G:

AG>Как это будет использовано практически?


AG>
{
  X x;
  x.write("hello"); // бросает
  x.close(); // пропускаем
} // сработал assert. как фиксим?

Если мы строго следуем первой стратегии, то здесь ещё надо try и catch:

{
  X x;
  try
  {
    x.write("hello");
  }
  catch (...)
  {
    try
    {
      x.close();
    }
    catch (...)
    {
      .... // как-то обрабатываем или просто замалчиваем
    }
    throw;
  }
  x.close();
}

Правда, наличие такой конструкции assert-ом, видимо, уже не проверишь

Другой вариант: считать, что в течение раскрутки стека ошибка закрытия всегда не нуждается в обработке. Тогда перед assert в деструкторе можно разместить проверку на наличие неперехваченных исключений.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.