在C++中是否有标准的类可以在作用域结束时将变量设置为特定值?

20

在成员函数的范围内,我想临时将一个成员变量设置为特定值。

然后,当此函数返回时,我希望将该成员变量重置为已知值。

为了防止异常和多次返回,并且我用一个简单的类似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++标准库是否有任何类模板可以实现此功能?


2
还有一个名为Boost.ScopeExit的工具。 - Baum mit Augen
2
@BaummitAugen:ScopeGuards的概念实际上是由Petru Marginean发明的,而不是Andrei Alexandresu。找不到权威参考资料,所以我只能相信Herb Sutter的说法 - IInspectable
2
@IInspectable 我不知道是谁发明了它,这只是我所知道的唯一实现。但感谢提供链接。此外,从标准提案中可以看到:“这个提案包含了Andrej Alexandrescu很久以前描述的作用域保护(scope guard)并在C++ Now 2012年再次解释。”如果我没记错的话,这就是我上面提到的演讲。 - Baum mit Augen
3
如果该对象被多个线程访问,这种结构将是致命的。 - Pete Becker
2
和“var = false;”一样致命的是@PeteBecker… - M.M
显示剩余6条评论
4个回答

11

目前还没有(但已有相关提议)。不过,实现通用的翻译很简单;

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规范中默认异常规范的说明相符。请参见cppreferenceSO Q&AGotW#47HIC++

可以找到其他的实现方式;


我会尽快添加更多的注释和异常规范。 - Niall
2
Peter Sommerlad所写的论文有新版本:P0052r1 - Kerrek SB
@KerrekSB。我很高兴这个提议没有被搁置。 - Niall

4
您可以使用 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应该没问题。


1
我没有点踩,但是当第一个构造函数参数为空时,删除者不一定会执行。 - Viktor Sehr
是的,那可能是一个原因,谢谢@Viktor。不过我测试了一下,好像可以工作。另一个原因可能是使用void,我猜这是否合法我不确定。 - alain
谢谢,现在我学到了一个新的原因,为什么using namespace std不好;-) - alain
谢谢你的信息,@Niall。但是将其复制到具有相同作用域的另一个shared_ptr中应该是可以的。(我猜这可能是无意中复制它的最常见情况。) - alain
@Niall,它能够正常工作并不是意外,这是shared_ptr的一个特性。相关的构造函数非常明确地说明了它拥有指针。 - Jonathan Wakely
显示剩余5条评论

1

这个没有标准版本。

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

E.19:使用final_action对象来表达清理操作,如果没有合适的资源处理程序可用

原因

finallytry/catch更简洁,更难出错。

示例

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

注意

finally不像try/catch那样混乱,但仍然是临时的。 请优先使用适当的资源管理对象。


1

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

在那个帖子中描述了一个调用任意函数的类似保护方法。为了解决您的问题,请调用一个重置变量的lambda表达式。

例如,对于您的代码,这个答案中的解决方案将是:

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

那个帖子上的另一个答案指出,类似的概念已被提议用于C++17标准化; 还有一个C++03解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接