为啥 Alexandrescu 不能使用 std::uncaught_exception() 在 ScopeGuard11 中实现 SCOPE_FAIL? [复制]

Posted

技术标签:

【中文标题】为啥 Alexandrescu 不能使用 std::uncaught_exception() 在 ScopeGuard11 中实现 SCOPE_FAIL? [复制]【英文标题】:Why can't Alexandrescu use std::uncaught_exception() to implement SCOPE_FAIL in ScopeGuard11? [duplicate]为什么 Alexandrescu 不能使用 std::uncaught_exception() 在 ScopeGuard11 中实现 SCOPE_FAIL? [复制] 【发布时间】:2013-02-02 08:29:41 【问题描述】:

毫无疑问,很多人都熟悉 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

在 2012 年及以后的 c++ 演讲中,他提到他无法找到正确检测范围是否因异常而退出的方法。因此,当且仅当范围因异常而退出时,他无法实现 SCOPE_FAIL 宏,该宏将执行提供的 lambda(通常用于回滚代码)。这将使dismiss() 成员函数变得不需要,并使代码更具可读性。

由于我绝不像 Alexandrescu 先生那样天才或经验丰富,我希望实施 SCOPE_FAIL 并不像这样容易:

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

我的问题是为什么不呢?

【问题讨论】:

很奇怪,有些东西告诉我它应该可以工作,但如果我尝试它,uncaught_exception() 总是返回false 我依稀记得 Herb Sutter 早在 GotW 上就有类似的东西,但我再也找不到了。也许是老年痴呆症;)或者我没有用谷歌搜索正确的东西。 我认为在作用域保护的情况下,你实际上可以使用std::uncaught_exception,因为作用域保护永远不会是另一个类的成员(当然也不是某个类的析构函数中的局部变量)。 @Xeo:还是std::uncaught_exception()seems to return false all the time。这可能是一个错误,还是我忽略了什么? @PorkyBrain: gotw.ca/gotw/047.htm ? 【参考方案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;

在离开 baz 时,您希望看到一个 rollback,但您会看到两个 - 包括离开 foo 时的一个虚假的。

【讨论】:

有趣。我想知道避免这种模式是否不是一个好的设计问题。在这里,foo() 永远不会抛出(否则从析构函数中调用它会很糟糕)。但如果它永远不会抛出,为什么要对异常使用回滚? foo() 设置为从不抛出,是为了演示。在一个真正的问题案例中,could_throw 可以抛出(原文如此!)的条件将不那么明显。在这种情况下,Bar 析构函数可以做try foo(); catch (...) 。或者中间可能有更多层,注意foo() 中的任何异常都不会离开Bar 析构函数。尽管如此,您会在foo() 中获得虚假回滚,即使它是通过常规返回留下的。 @AndyProwl:我已经调整了示例以使其更清晰。 有道理。但是,在这种情况下ScopeGuard11 的构造函数可以检查uncaught_exception() 是否返回true,并且在这种情况下保存std::current_exception() 的结果。在析构函数中,它会再次调用std::current_exception() 并将结果与​​之前存储的异常指针进行比较。如果相等,它不会做任何事情。如果不同,它将调用回滚函数。有意义吗? @ecatmur:可能是的,但也需要更改为current_exception()。目前,current_exception() 在控制权转移到异常处理程序之前返回nullptr。不过,我仍然在想,如果在抛出异常但尚未处理的情况下适当注意不使用异常保护调用函数,则 OP 的解决方案是可以接受的。如果uncaught_exception() 返回true,我会说ScopeGuard11 的构造函数甚至可以断言(或自身抛出异常)。但我只是在大声思考,不确定这是否是个好主意。

以上是关于为啥 Alexandrescu 不能使用 std::uncaught_exception() 在 ScopeGuard11 中实现 SCOPE_FAIL? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥 std::abs() 不能与浮点数一起使用

为啥不能对 std::vector 使用前向声明?

为啥我不能将 std::string 与 libcurl 一起使用?

为啥 printf() 可以在内核中工作,但使用 std::cout 不能?

为啥 printf() 可以在内核中工作,但使用 std::cout 不能?

为啥 std::async 不能与接收抽象类引用作为参数的函数一起使用?