19

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

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

为了防止异常和多次返回,我用一个简单的 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++ 标准库中是否有这样的模板类?

4

4 回答 4

10

还没有(已经提出了这方面的建议)。但是实现一个通用的就足够简单了。

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 实现一致
  • 我已经修改了C++ 提案中的大部分动机noexcept,更重要的细节
  • 析构函数的“通常” RAII 语义,因此“范围退出”函数适用;它不会throw,这也与 C++11 中关于析构函数的默认异常规范的规范一致。请参阅cppreferenceSO Q&AGotW#47HIC++

可以找到其他实现;

于 2016-04-15T10:20:48.157 回答
4

shared_ptr您可以为此“滥用” :

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

void如果对使用as template parameter有疑虑T,我在 C++ 标准中找到了以下内容:

20.8.2.2§2:

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

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

于 2016-04-15T10:29:22.633 回答
1

这个没有标准版本。

CppGoreGuidelines支持库 ( GSL ) 有一个通用版本,称为finally,但该库尚未达到生产质量它绝对推荐的做法。

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

原因

finallytry/更简洁,更难出错catch

例子

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

笔记

finally不像try/那样混乱catch,但它仍然是临时的。首选适当的资源管理对象。

于 2016-04-15T10:33:23.693 回答
1

类似问题:最简单最整洁的 c++11 ScopeGuard

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

例如,您的代码的此答案的解决方案是:

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

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

于 2016-04-18T11:36:42.283 回答