Почему Александреску не может использовать 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,

Другие вопросы по тегам