Почему Александреску не может использовать std::uncaught_exception() для реализации SCOPE_FAIL в ScopeGuard11?
Многие люди, без сомнения, знакомы с шаблоном Mr. Alexandrescus ScopeGuard (теперь он является частью Loki) и новой версией ScopeGuard11, представленной здесь: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
с источником здесь: https://gist.github.com/KindDragon/4650442
В своем выступлении на C++ и после 2012 года он упомянул, что не может найти способ правильно определить, выходит ли область действия из-за исключения. Поэтому он не мог реализовать макрос SCOPE_FAIL, который выполнял бы поставляемую лямбду (обычно используемую для кода отката) тогда и только тогда, когда область действия вышла из-за исключения. Это сделает ненужной функцию-член dismiss() и сделает код более читабельным.
Поскольку я ни в коем случае не такой гений или опыт, как г-н Александреску, я ожидаю, что реализовать SCOPE_FAIL не так просто, как это:
~ScopeGuard11(){ //destructor
if(std::uncaught_exception()){ //if we are exiting because of an exception
f_(); //execute the functor
}
//otherwise do nothing
}
Мой вопрос почему нет?
1 ответ
С ScopeGuard11
класс, в котором есть ваш деструктор, член f_
может вызываться, даже если это не текущая область (которая должна быть защищена защитником), которая выходит из-за исключения. Использование этой защиты небезопасно в коде, который может использоваться во время очистки исключений.
Попробуйте этот пример:
#include <exception>
#include <iostream>
#include <string>
// simplified ScopeGuard11
template <class Fun>
struct ScopeGuard11 {
Fun f_;
ScopeGuard11(Fun f) : f_(f) {}
~ScopeGuard11(){ //destructor
if(std::uncaught_exception()){ //if we are exiting because of an exception
f_(); //execute the functor
}
//otherwise do nothing
}
};
void rollback() {
std::cout << "Rolling back everything\n";
}
void could_throw(bool doit) {
if (doit) throw std::string("Too bad");
}
void foo() {
ScopeGuard11<void (*)()> rollback_on_exception(rollback);
could_throw(false);
// should never see a rollback here
// as could throw won't throw with this argument
// in reality there might sometimes be exceptions
// but here we care about the case where there is none
}
struct Bar {
~Bar() {
// to cleanup is to foo
// and never throw from d'tor
try { foo(); } catch (...) {}
}
};
void baz() {
Bar bar;
ScopeGuard11<void (*)()> more_rollback_on_exception(rollback);
could_throw(true);
}
int main() try {
baz();
} catch (std::string & e) {
std::cout << "caught: " << e << std::endl;
}
Вы хотели бы увидеть один rollback
при выходе baz
, но вы увидите два - в том числе ложный от ухода foo
,