问题 是否有C ++标准类将变量设置为范围出口处的值


在成员函数的范围内,我想暂时将成员变量设置为某个值。

然后,当此函数返回时,我想将此成员变量重置为给定的已知值。

为了防止异常和多次返回,我已经使用类似简单的RAII来完成它。它是在成员函数的范围内定义的。

void MyClass::MyMemberFunction() {
    struct SetBackToFalse {
        SetBackToFalse(bool* p): m_p(p) {}
        ~SetBackToFalse() {*m_p=false;}
    private:
        bool* m_p;
    };

    m_theVariableToChange = true;
    SetBackToFalse resetFalse( &m_theVariableToChange ); // Will reset the variable to false.

    // Function body that may throw.
}

看起来很平常,我想知道是否有任何这样的模板类在C ++标准库中这样做?


6067
2018-04-15 10:10


起源

还有 Boost.ScopeExit。 - Baum mit Augen
@BaummitAugen:概念 ScopeGuards 真的是由Petru Marginean发明的,而不是Andrei Alexandresu。找不到权威参考,所以我不得不采取 赫伯萨特的话。 - IInspectable
@IInspectable我不知道是谁发明了它,它只是我所知道的唯一实现。但是对于链接而言。另外,来自 标准提议: “这个提议融合了Andrej Alexandrescu很久以前所描述的范围内容,并在C ++ Now 2012()中再次解释。” Iirc这就是我上面提到的话题。 - Baum mit Augen
如果从多个线程访问对象,则此构造绝对致命。 - Pete Becker
@PeteBecker同样致命 var = false; ... - M.M


答案:


尚未(已提出此建议)。但实现通用的很简单;

struct scope_exit {
  std::function<void()> f_;
  explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
  ~scope_exit() { if (f_) f_(); }
};
// ...
m_theVariableToChange = true;
scope_exit resetFalse([&m_theVariableToChange]() { m_theVariableToChange = false; });

为简单起见,我已经编辑了副本并移动构造函数等...

将它们标记为 = delete 将使上述最小化解决方案。进一步;如果需要可以允许移动,但应禁止复制。


更完整 scope_exit 看起来像(在线演示);

template <typename F>
struct scope_exit {
  F f_;
  bool run_;
  explicit scope_exit(F f) noexcept : f_(std::move(f)), run_(true) {}
  scope_exit(scope_exit&& rhs) noexcept : f_((rhs.run_ = false, std::move(rhs.f_))), run_(true) {}
  ~scope_exit()
  {
    if (run_)
      f_(); // RAII semantics apply, expected not to throw
  }

  // "in place" construction expected, no default ctor provided either
  // also unclear what should be done with the old functor, should it
  // be called since it is no longer needed, or not since *this is not
  // going out of scope just yet...
  scope_exit& operator=(scope_exit&& rhs) = delete;
  // to be explicit...
  scope_exit(scope_exit const&) = delete;
  scope_exit& operator=(scope_exit const&) = delete;
};

template <typename F>
scope_exit<F> make_scope_exit(F&& f) noexcept
{
  return scope_exit<F>{ std::forward<F>(f) };
}

关于执行情况的说明;

  • std::function<void()> 可以用来擦除仿函数的类型。 std::function<void()> 基于所保持函数的特定异常,在移动构造函数上提供异常保证。找到了此实现的示例 这里
  • 这些异常规范与C ++提议和GSL实现一致
  • 我已经完成了大部分动机 noexcept,更多的细节发现在 C ++提案
  • 析构函数的“通常”RAII语义,因此“范围退出”功能适用;它不会 throw,这也与析构函数的默认异常规范上的C ++ 11规范一致。看到 cppreference所以Q&AGotW#47 和 HIC ++

可以找到其他实现;


8
2018-04-15 10:20



只有在忽略异常时才会“完成”... - Kerrek SB
一旦有机会我会添加更多注释和例外规范。 - Niall
(Peter Sommerlad关于你所指的论文有一个新版本: P0052r1。) - Kerrek SB
@KerrekSB。我很高兴这个建议没有落在路边。 - Niall


你可以'滥用' shared_ptr 为了这:

m_theVariableToChange = true;
std::shared_ptr<void> resetFalse(nullptr, [&](void*){ m_theVariableToChange = false; });

如果有使用担忧 void 作为模板参数 T,我在C ++标准中发现了以下内容:

20.8.2.2§2:

... shared_ptr的模板参数T可以是不完整类型。

这表明了 T 仅用作指针,因此使用 void 应该没事。


4
2018-04-15 10:29



downvote的原因是什么? (我想知道我是否犯了错误......) - alain
我没有downvote,但可能不保证删除器将在第一个构造函数参数为null时执行 - Viktor Sehr
是的,这可能是一个原因,谢谢@Viktor。但是,我测试了它,它似乎工作。另一个原因可能是使用 void 我猜。我不确定这是否合法。 - alain
亲爱的downvoter,如果原因是 std::nullptr,现在已经纠正了。愚蠢的错误,对不起。 - alain
谢谢,现在我学会了一个新原因 using namespace std 不好 ;-) - alain


没有标准版本。

CppGoreGuidelines 支持库(GSL)有一个被称为的通用版本 最后 但那个图书馆还没有生产质量。绝对推荐的做法。

E.19: 用一个 final_action 如果没有合适的资源句柄,则表示清理的对象

原因

finally 不那么冗长,也不容易弄错 try/catch

void f(int n)
{
    void* p = malloc(1, n);
    auto _ = finally([p] { free(p); });
    // ...
}

注意

finally 并不像它那样凌乱 try/catch,但它仍然是临时的。 喜欢适当的资源管理对象。


1
2018-04-15 10:33





类似的问题: 最简单,最新的c ++ 11 ScopeGuard

在该线程上描述了一个用于调用任意函数的类似保护。要解决您的问题,请调用一个重置您的变量的lambda。

例如,解决方案来自 这个答案 对于你的代码将是:

scope_guard guard1 = [&]{ m_theVariableToChange = false; };

该线程的另一个答案指出,已经为C ++ 17标准化提出了类似的概念;并且还提供了一个C ++ 03解决方案。


1
2018-04-18 11:36