Эксепшн из деструктора
От: Аноним  
Дата: 23.11.11 12:44
Оценка: :)))
Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
Re: Эксепшн из деструктора
От: XuMuK Россия  
Дата: 23.11.11 12:55
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


потому что деструктор может вызваться из-за брошенного исключения, в этом случае выкинутое исключение из деструктора даст сразу два необработанных исключения и неопределенное поведение с точки зрения стандарта. наличие необработанного исключения можно проверить функцией std::uncaught_exception.
Re: Эксепшн из деструктора
От: ibnhatab Украина  
Дата: 23.11.11 13:01
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


Отработать отработало, но по рукам за такое бить надо. Корень зла лежит в том, что при выбрасывании exception происходит раскрутка стека (если он обрабатывается), которая в свою очередь обещает вызвать деструкторы всех обьектов в области try секции. А теперь представим что вы бросили exception в try и начинается раскрутка стека и вылетает exception в одном или более деструкторов. Вопрос: какой тип exception вы будете обрабатывать? Как вы будете обрабатывать? В каком порядке?

Например в VS вы сможете отловить только последний брошенный exception (сильно подозреваю что в gcc ситуация та же). Так как у вас не будет возможности обработать все exception-ы такой стиль программирования считается дурным тоном.
Re[2]: Эксепшн из деструктора
От: Тот кто сидит в пруду Россия  
Дата: 23.11.11 13:03
Оценка: +1
Здравствуйте, XuMuK, Вы писали:

А>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


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


Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: Эксепшн из деструктора
От: uzhas Ниоткуда  
Дата: 23.11.11 13:16
Оценка:
Здравствуйте, Аноним, Вы писали:

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

можно, но очень осторожно
Re: Эксепшн из деструктора
От: anonymouss  
Дата: 23.11.11 13:20
Оценка: 1 (1)
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


пример
Нажми кнопочку Submit, посмотри результат.
Раскоментируй //throw std::runtime_error("Game over!");
Снова нажми кнопочку Submit, посмотри результат.
Re[3]: Эксепшн из деструктора
От: ibnhatab Украина  
Дата: 23.11.11 13:21
Оценка:
Здравствуйте, Тот кто сидит в пруду, Вы писали:

ТКС>Здравствуйте, XuMuK, Вы писали:


А>>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


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


ТКС>Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.


А откуда ему там взятся если стоит catch(...)?
Re[4]: Эксепшн из деструктора
От: Тот кто сидит в пруду Россия  
Дата: 23.11.11 13:32
Оценка:
Здравствуйте, ibnhatab, Вы писали:

А>>>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


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


ТКС>>Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.


I>А откуда ему там взятся если стоит catch(...)?


15.2.3 —

If a destructor called during stack unwinding exits with an exception, std::terminate is called

Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: Эксепшн из деструктора
От: night beast СССР  
Дата: 23.11.11 13:58
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


http://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/
http://akrzemi1.wordpress.com/2011/09/28/who-calls-stdterminate/
http://akrzemi1.wordpress.com/2011/10/05/using-stdterminate/
Re: Эксепшн из деструктора
От: Кодт Россия  
Дата: 23.11.11 14:53
Оценка: 4 (1)
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


Даже если исключение кинуть не во время раскрутки стека, а в штатном режиме, — программе может сплохеть.
Пример (http://codepad.org/YnX8CuMZ)
struct Some { int t; ~Some() { cout << t << " "; } };

struct X : Some
{
  Some member;
  bool e;
  X() : e(false) {}
  ~X() { if(e) throw 123; }
};

int main()
{
  try
  {
    X x[10]; // последовательно конструируются x[0], x[1], ..., x[9]
    for(int i=0; i!=10; ++i) { x[i].t = i*10; x[i].member.t = i*10 + 1; }
    x[5].e = true;
  }          // последовательно разрушаются x[9], x[8], ..., x[5], в т.ч. его члены и базы в рамках размотки.
             // остальные x[4]...x[0] проигнорированы
  catch(...) {}
}

вывод: 91 90 81 80 71 70 61 60 51 50
Перекуём баги на фичи!
Re[5]: Эксепшн из деструктора
От: ibnhatab Украина  
Дата: 23.11.11 18:19
Оценка:
Здравствуйте, Тот кто сидит в пруду, Вы писали:

ТКС>Здравствуйте, ibnhatab, Вы писали:


А>>>>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.


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


ТКС>>>Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.


I>>А откуда ему там взятся если стоит catch(...)?


ТКС>15.2.3 —

ТКС>

ТКС>If a destructor called during stack unwinding exits with an exception, std::terminate is called


Сдается мне что это фича нового стандарта. В С++ 98 такого вроде бы нету
Re[2]: Эксепшн из деструктора
От: night beast СССР  
Дата: 23.11.11 19:14
Оценка: 1 (1)
Здравствуйте, Кодт, Вы писали:

К>Даже если исключение кинуть не во время раскрутки стека, а в штатном режиме, — программе может сплохеть.

К>Пример (http://codepad.org/YnX8CuMZ)
К>вывод: 91 90 81 80 71 70 61 60 51 50

это не баг случайно?
в студии 2005 все отработали.
да и по стандарту вроде все должны (15.2 2)
Re[6]: Эксепшн из деструктора
От: Тот кто сидит в пруду Россия  
Дата: 23.11.11 21:49
Оценка:
Здравствуйте, ibnhatab, Вы писали:

ТКС>>>>Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.


I>>>А откуда ему там взятся если стоит catch(...)?


ТКС>>15.2.3 —

ТКС>>

ТКС>>If a destructor called during stack unwinding exits with an exception, std::terminate is called


I>Сдается мне что это фича нового стандарта. В С++ 98 такого вроде бы нету


Точно было.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[3]: Эксепшн из деструктора
От: Кодт Россия  
Дата: 24.11.11 07:36
Оценка:
Здравствуйте, night beast, Вы писали:

NB>это не баг случайно?

NB>в студии 2005 все отработали.
NB>да и по стандарту вроде все должны (15.2 2)

Пусть это и баг gcc, но тем не менее... Этот баг не только на кодепаде воспроизводится, но и в линуксе (gcc 4.6.1)

Кстати, та же фигня при delete[].
Перекуём баги на фичи!
Re[4]: Эксепшн из деструктора
От: night beast СССР  
Дата: 24.11.11 07:40
Оценка:
Здравствуйте, Кодт, Вы писали:

NB>>это не баг случайно?

NB>>в студии 2005 все отработали.
NB>>да и по стандарту вроде все должны (15.2 2)

К>Пусть это и баг gcc, но тем не менее... Этот баг не только на кодепаде воспроизводится, но и в линуксе (gcc 4.6.1)

К>Кстати, та же фигня при delete[].

да я не спорю
кстати, если массив заменить на array<10>
template<int Size> struct array : array<Sise-1> { X & operator[] (int); };

то все работает...
Re[5]: Эксепшн из деструктора
От: Кодт Россия  
Дата: 24.11.11 09:29
Оценка:
Здравствуйте, night beast, Вы писали:

NB>кстати, если массив заменить на array<10>

NB>
NB>template<int Size> struct array : array<Sise-1> { X & operator[] (int); };
NB>

NB>то все работает...

Вот это извращение!
Кстати говоря, а нормальные контейнеры, такие как std::vector, пытаются вручную воспроизвести то поведение, которым должен обладать голый массив (хотя и не обладает, судя по gcc)?

Там ведь процедура выглядит примерно так
size_t n = size;
while(n!=0) call_destructor(data[--n]); // вылетело - так уж вылетело
free(data);

А с учётом продолжения после исключений
size_t n = size;
try
{
  while(n!=0) call_destructor(data[--n]);
}
catch(...)
{
  try
  {
    while(n!=0) call_destructor(data[--n]);
  }
  catch(...) { terminate(); }
  throw;
}
free(data);

Или, чисто на деструкторах,
struct do_destruct
{
  T*& data;
  size_t& n;
  do_destruct(T*& data, size_t& n) : data(data), n(n) {}
  ~do_destruct()
  {
    if(!data) return;
    while(n) data[--n].~T();
    free(data);
    data = 0;
  }
};

........

{
  do_destruct second_chance(data,n);
  do_destruct first_attempt(data,n);
}

Сперва отработает first_attempt; если он вылетит, то отработает second_chance; а если и он вылетит, то это уже будет исключение во время раскрутки, ведущее к terminate.
Перекуём баги на фичи!
Re[6]: Эксепшн из деструктора
От: night beast СССР  
Дата: 24.11.11 11:31
Оценка:
Здравствуйте, Кодт, Вы писали:

NB>>кстати, если массив заменить на array<10>

NB>>
NB>>template<int Size> struct array : array<Sise-1> { X & operator[] (int); };
NB>>

NB>>то все работает...

К>Вот это извращение!

К>Кстати говоря, а нормальные контейнеры, такие как std::vector, пытаются вручную воспроизвести то поведение, которым должен обладать голый массив (хотя и не обладает, судя по gcc)?

студия и гсс нет.
да и не особо и нужно. не дело это, из деструктора кидаться
сейчас стандарта нет под рукой посмотреть какие ограничения на типы накладываются
Re[6]: Эксепшн из деструктора
От: Masterkent  
Дата: 24.11.11 16:43
Оценка: 32 (1)
Кодт:

К>Кстати говоря, а нормальные контейнеры, такие как std::vector, пытаются вручную воспроизвести то поведение, которым должен обладать голый массив (хотя и не обладает, судя по gcc)?


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

C++11 — Table 96 — Container requirements:

Expression: X::value_type
Return type: T
Assertion/note pre-/post-condition: Requires: T is Destructible

Table 24 — Destructible requirements:

Expression: u.~T()
Post-condition: All resources owned by u are reclaimed, no exception is propagated.

17.6.4.8:

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:

[...]

— for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (17.6.3.5, 23.2, 24.2, 26.2). Operations on such types can report a failure by throwing an exception unless otherwise specified.

— if any replacement function or handler function or destructor operation exits via an exception, unless specifically allowed in the applicable Required behavior: paragraph.

Re: Эксепшн из деструктора
От: Cyberax Марс  
Дата: 25.11.11 01:22
Оценка: -1
Здравствуйте, Аноним, Вы писали:

А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.

Вполне можно, более того, иногда полезно.

У меня вот так сделана обработка исключений:
    /**
      Usage - err(sOk) << "This is a description"
      */
    struct err : public std::stringstream
    {
        err(result_code_t::code_e code) : code_(code) {}
        ~err()
        {
            //Yes, we're throwing from a destructor
            if (code_!=result_code_t::sOk)
            {
                boost::throw_exception(my_exception(result_code_t(code_, str())));
            }
        }

    private:
        result_code_t::code_e code_;
    };

    /**
      Usage: some_function(...) | die;
      */     
    inline void operator | (const result_code_t &code, const die_t &)
    {
        if (!code.ok())
            boost::throw_exception(my_exception(code));
    }


Стоит понимать, что проблема возникнет, если деструктор будет выполняться из-за того, что разматывается стек из-за уже брошенного исключения.
Sapienti sat!
Re[2]: Эксепшн из деструктора
От: Masterkent  
Дата: 25.11.11 09:19
Оценка:
Cyberax:

А>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.

C>Вполне можно, более того, иногда полезно.

C>У меня вот так сделана обработка исключений:

C>
C>    /**
C>      Usage - err(sOk) << "This is a description"
C>      */
C>    struct err : public std::stringstream
C>    {
C>        err(result_code_t::code_e code) : code_(code) {}
C>        ~err()
C>        {
C>            //Yes, we're throwing from a destructor
C>            if (code_!=result_code_t::sOk)
C>            {
C>                boost::throw_exception(my_exception(result_code_t(code_, str())));
C>            }
C>        }

C>    private:
C>        result_code_t::code_e code_;
C>    };

C>    /**
C>      Usage: some_function(...) | die;
C>      */     
C>    inline void operator | (const result_code_t &code, const die_t &)
C>    {
C>        if (!code.ok())
C>            boost::throw_exception(my_exception(code));
C>    }
C>

По правилам C++11 с учётом их планируемой корректировки (см. ниже) попытка выкинуть исключение из такого деструктора должна привести к вызову std::terminate. Дело в том, что отсутствие явного указания exception-specification в данном случае равноценно указанию noexcept(true), т.е. такой деструктор обязуется не бросать исключения. Чтобы разрешить ему бросать исключения, нужно указать соответствующую exception-specification.

12.4/3:

A declaration of a destructor that does not have an exception-specification is implicitly considered to have the same exception-specification as an implicit declaration (15.4).

15.4/14:

An implicitly declared special member function (Clause 12) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, move constructor, destructor, copy assignment operator, or move assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a function directly invoked by f’s implicit definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions.

Фактически текущие правила не оговаривают, какую именно exception-specification должна иметь special member function в последнем случае: throw() или noexcept(true) — от этого зависит будет ли в случае выброса исключения вызвана std::unexpected или std::terminate. По задумке комитета должно быть именно noexcept(true) — см. N3204: Deducing "noexcept" for destructors и Issue 1282: Underspecified destructor exception-specification

В более ранних документах вкратце описано, почему для деструкторов было решено ввести подобные правила:
N3092 comment GB 40
N3166: Destructors default to noexcept
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.