2013-02-17 21 views
16

Многие люди, без сомнения, знакомы с шаблоном г-н Alexandrescus ScopeGuard (теперь часть Локи) и новой версии ScopeGuard11 представлены здесь: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-CПочему Alexandrescu не использует std :: uncaught_exception() для реализации SCOPE_FAIL в ScopeGuard11?

с источником здесь: https://gist.github.com/KindDragon/4650442

В своей речи на C++ и после 2012 года он упомянул, что не смог найти способ правильно определить, была ли исключена область из-за исключения. Поэтому он не смог реализовать макрос SCOPE_FAIL, который выполнил бы предоставленную лямбду (обычно используемую для обратного кода) тогда и только тогда, когда область завершилась из-за исключения. Это приведет к тому, что функция члена() будет отключена и сделает код более удобочитаемым.

Поскольку я ни в коем случае, как гений или переживается как г-н Alexandrescu я ожидаю реализации SCOPE_FAIL не так просто, как это:

~ScopeGuard11(){      //destructor 
    if(std::uncaught_exception()){ //if we are exiting because of an exception 
     f_();       //execute the functor 
    } 
    //otherwise do nothing 
} 

Мой вопрос, почему нет?

+0

Странно, что-то говорит мне, что он должен работать, но если я попробую, 'uncaught_exception()' всегда возвращает 'false'. –

+0

Я смутно помню, как Герб Саттер имел что-то подобное на обратном пути, когда я не могу найти его больше. Может быть, болезнь Альцгеймера;) или я не собираюсь делать правильные вещи. – odinthenerd

+0

Я думаю, что в случае с защитой сферы вы могли бы использовать 'std :: uncaught_exception', так как защита области никогда не будет членом другого класса (и, конечно же, не локальной переменной в деструкторе класса). – Xeo

ответ

11

С классом 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; 
} 

Вы хотели бы увидеть одну rollbackbaz при выходе, но вы увидите два - в том числе и паразитных один из оставляя foo.

+1

Интересно. Мне интересно, если это не вопрос хорошего дизайна, чтобы избежать этого шаблона. Здесь 'foo()' никогда не будет бросать (в противном случае было бы плохо назвать это из деструктора). Но если он никогда не бросит, зачем использовать откат для исключения? –

+0

Это 'foo()' было настроено так, чтобы никогда не бросать, было ради демонстрации. В реальном проблемном случае условия, когда 'can_throw' мог бы сбрасывать (sic!), Были бы менее очевидны. В этом случае деструктор 'Bar' мог бы выполнить' try {foo(); } catch (...) {} '. Или может быть больше слоев между ними, если учесть, что любое исключение в 'foo()' не оставляет деструктора 'Bar'. Тем не менее вы получите ложный откат в 'foo()', даже если он оставлен регулярным возвратом. – JoergB

+0

@AndyProwl: Я скорректировал этот пример, чтобы сделать это понятным. – JoergB