c++ - ¿Por qué no puede Alexandrescu usar std:: uncaught_exception() para implementar SCOPE_FAIL en ScopeGuard11?
c++11 raii (1)
Esta pregunta ya tiene una respuesta aquí:
- ¿Alcance (falla) en C ++ 11? 2 respuestas
Sin duda, muchas personas están familiarizadas con la plantilla ScopeGuard del Sr. Alexandrescus (ahora parte de Loki) y la nueva versión ScopeGuard11 que se presenta aquí: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
con la fuente aquí: https://gist.github.com/KindDragon/4650442
En su charla en c ++ y después de 2012, mencionó que no podía encontrar una manera de detectar correctamente si se estaba saliendo del alcance debido a una excepción. Por lo tanto, no pudo implementar una macro SCOPE_FAIL que ejecutaría la lambda provista (usualmente utilizada para el código de retroceso) si y solo si el alcance se cerrara debido a una excepción. Esto haría innecesaria la función miembro de despedir () y haría que el código sea más legible.
Ya que no soy de ninguna manera tan genial o experimentado como el Sr. Alexandrescu, espero que la implementación de SCOPE_FAIL no sea tan fácil como esto:
~ScopeGuard11(){ //destructor
if(std::uncaught_exception()){ //if we are exiting because of an exception
f_(); //execute the functor
}
//otherwise do nothing
}
Mi pregunta es ¿por qué no?
Con una clase de ScopeGuard11
que tiene su destructor, se puede llamar al miembro f_
, incluso si no es el ámbito actual (que se supone que está protegido por la guardia) que se está saliendo debido a una excepción. El uso de este protector no es seguro en el código que podría usarse durante la limpieza de excepciones.
Prueba este ejemplo:
#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;
}
Usted querría ver una rollback
cuando salga de baz
, pero verá dos, incluyendo una espuria para dejar foo
.