Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
Кодт:
К>Кстати говоря, а нормальные контейнеры, такие как 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.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 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(...) {}
}
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
пример
Нажми кнопочку Submit, посмотри результат.
Раскоментируй //throw std::runtime_error("Game over!");
Снова нажми кнопочку Submit, посмотри результат.
Здравствуйте, Кодт, Вы писали:
К>Даже если исключение кинуть не во время раскрутки стека, а в штатном режиме, — программе может сплохеть. К>Пример (http://codepad.org/YnX8CuMZ) К>вывод: 91 90 81 80 71 70 61 60 51 50
это не баг случайно?
в студии 2005 все отработали.
да и по стандарту вроде все должны (15.2 2)
Здравствуйте, XuMuK, Вы писали:
А>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
XMK>потому что деструктор может вызваться из-за брошенного исключения, в этом случае выкинутое исключение из деструктора даст сразу два необработанных исключения и неопределенное поведение с точки зрения стандарта. наличие необработанного исключения можно проверить функцией std::uncaught_exception.
Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 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 destructorif (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));
}
Стоит понимать, что проблема возникнет, если деструктор будет выполняться из-за того, что разматывается стек из-за уже брошенного исключения.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
потому что деструктор может вызваться из-за брошенного исключения, в этом случае выкинутое исключение из деструктора даст сразу два необработанных исключения и неопределенное поведение с точки зрения стандарта. наличие необработанного исключения можно проверить функцией std::uncaught_exception.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
Отработать отработало, но по рукам за такое бить надо. Корень зла лежит в том, что при выбрасывании exception происходит раскрутка стека (если он обрабатывается), которая в свою очередь обещает вызвать деструкторы всех обьектов в области try секции. А теперь представим что вы бросили exception в try и начинается раскрутка стека и вылетает exception в одном или более деструкторов. Вопрос: какой тип exception вы будете обрабатывать? Как вы будете обрабатывать? В каком порядке?
Например в VS вы сможете отловить только последний брошенный exception (сильно подозреваю что в gcc ситуация та же). Так как у вас не будет возможности обработать все exception-ы такой стиль программирования считается дурным тоном.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему?
можно, но очень осторожно
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>Здравствуйте, XuMuK, Вы писали:
А>>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
XMK>>потому что деструктор может вызваться из-за брошенного исключения, в этом случае выкинутое исключение из деструктора даст сразу два необработанных исключения и неопределенное поведение с точки зрения стандарта. наличие необработанного исключения можно проверить функцией std::uncaught_exception.
ТКС>Чоэто вдруг неопределенное? std::terminate() — очень даже определенное поведение.
Здравствуйте, 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 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Аноним, Вы писали:
А>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>Здравствуйте, 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 такого вроде бы нету
Здравствуйте, 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 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Кодт, Вы писали:
NB>>это не баг случайно? NB>>в студии 2005 все отработали. NB>>да и по стандарту вроде все должны (15.2 2)
К>Пусть это и баг gcc, но тем не менее... Этот баг не только на кодепаде воспроизводится, но и в линуксе (gcc 4.6.1) К>Кстати, та же фигня при delete[].
да я не спорю
кстати, если массив заменить на array<10>
Вот это извращение!
Кстати говоря, а нормальные контейнеры, такие как std::vector, пытаются вручную воспроизвести то поведение, которым должен обладать голый массив (хотя и не обладает, судя по gcc)?
Там ведь процедура выглядит примерно так
size_t n = size;
while(n!=0) call_destructor(data[--n]); // вылетело - так уж вылетело
free(data);
Сперва отработает first_attempt; если он вылетит, то отработает second_chance; а если и он вылетит, то это уже будет исключение во время раскрутки, ведущее к terminate.
NB>>то все работает...
К>Вот это извращение! К>Кстати говоря, а нормальные контейнеры, такие как std::vector, пытаются вручную воспроизвести то поведение, которым должен обладать голый массив (хотя и не обладает, судя по gcc)?
студия и гсс нет.
да и не особо и нужно. не дело это, из деструктора кидаться
сейчас стандарта нет под рукой посмотреть какие ограничения на типы накладываются
Cyberax:
А>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало. C>Вполне можно, более того, иногда полезно.
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
Здравствуйте, Masterkent, Вы писали:
M>По правилам C++11 с учётом их планируемой корректировки (см. ниже) попытка выкинуть исключение из такого деструктора должна привести к вызову std::terminate. Дело в том, что отсутствие явного указания exception-specification в данном случае равноценно указанию noexcept(true), т.е. такой деструктор обязуется не бросать исключения. Чтобы разрешить ему бросать исключения, нужно указать соответствующую exception-specification.
Блин, они не могут угомониться с этими исключениями. Хотя, конечно, noexcept добавить несложно.
Здравствуйте, Cyberax, Вы писали:
А>>Недавно на собеседовании спросили, почему нельзя кидать исключение из деструктора. А, собственно, почему? Накидал в 2005-й студии тестовый код, вроде все нормально отработало. C>Вполне можно, более того, иногда полезно.
C>У меня вот так сделана обработка исключений: C>
C>Стоит понимать, что проблема возникнет, если деструктор будет выполняться из-за того, что разматывается стек из-за уже брошенного исключения.
Ха, я примерно такую же фигню залудить хотел, но чето взяли меня сомнения, не будет ли при таком использовании теряться содержимое stringstream. А выяснять поленился.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Cyberax, Вы писали:
C>У меня вот так сделана обработка исключений:
die — хорошая идея. У меня тоже такое было пока я работал с функциями возвращающими коды ошибок. А вот err — не очень. Создание временного stringstream и форматирование сообщения об ошибке происходит вне зависимости от того, произошла ошибка или нет.
Здравствуйте, alexeiz, Вы писали:
C>>У меня вот так сделана обработка исключений: A>die — хорошая идея. У меня тоже такое было пока я работал с функциями возвращающими коды ошибок. А вот err — не очень. Создание временного stringstream и форматирование сообщения об ошибке происходит вне зависимости от того, произошла ошибка или нет.
Оно у меня обычно используется так:
if (some_bad_condition())
err(badConditionCode) << "Something bad!";
Если что, можно сделать макрос:
#define ERR(scode) if (!is_success(scode)) err(scode)
...
ERR(code_failed) << "Something bad";